{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "delta-frame-macroblock",
  "title": "Delta Frame Macroblock",
  "description": "A quiet full-width band that visualizes a video codec's own P-frame change-detection decision: a grid of macroblocks sits mostly static, and every 220ms tick a small, capped subset is flagged changed with a brief outline flash and a slightly shifted tone while every other block is explicitly left untouched, exactly the sparse per-block change map a real codec decodes.",
  "dependencies": [],
  "files": [
    {
      "path": "registry/core/delta-frame-macroblock/component.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useRef } from \"react\";\n\n// ---------------------------------------------------------------------------\n// DeltaFrameMacroblock — a quiet full-width band that visualizes a video\n// codec's own P-frame change-detection decision, not the pixels it redraws.\n//\n// After an MPEG/H.26x-class codec sends one full (I-)frame, every frame after\n// that only encodes the macroblocks that actually changed; each block carries\n// a \"redraw\" or \"skip, unchanged\" flag, and a decoder leaves every skipped\n// block's pixels exactly as they were in the frame buffer. Scrub a real\n// codec's per-block change map and you see a sparse, shifting set of flagged\n// blocks against a mostly static field — the compression artifact visible as\n// blockiness in low-bitrate video, made deliberate here instead of a defect.\n//\n// A uniform grid of macroblocks is sized off the container's SMALLER\n// dimension: block = clamp(round(minDim / 16), 12, 24) px, then cols/rows are\n// rounded so the grid fills the container exactly (a 320px band at 16px\n// blocks holds a 20-block-wide row). Each block carries a fixed per-block\n// seed tone (drawn once, never reseeded) plus a bounded, reflecting\n// toneOffset that only moves the tick a block is flagged — that is the\n// \"slightly shifted tone\" of a motion-compensated redraw, and reflecting the\n// offset at its amplitude ceiling keeps the drift visible forever without\n// ever saturating into salt-and-pepper noise (an unbounded random walk would\n// eventually finish, which fails the always-different-at-rest rule).\n//\n// A fixed 220ms tick — the visualized P-frame interval, deliberately slower\n// than a real ~30fps codec tick per the round 9 decoupling rule, so each\n// delta event stays an individually followable discrete flash rather than a\n// strobe — flags a SMALL, capped number of blocks as \"changed\" (2-4 blocks\n// regardless of grid size, not the raw 6-10% the source ratio would suggest:\n// at typical card-scale grids of 150-400 blocks, 6-10% is 10-30 simultaneous\n// flags, and against a 340ms outline lifetime that overlaps into a\n// continuous shimmer rather than discrete countable events — the spec's own\n// kill criteria pre-authorizes making the timing more aggressive rather than\n// shipping that). A flagged block's outline appears instantly, holds at full\n// --foreground-weight stroke for 140ms, then fades over the next 200ms back\n// to unoutlined — 340ms total, comfortably inside the 220ms-spaced ticks so\n// at most two flash generations ever overlap.\n//\n// The whole board is redrawn every animation frame from typed-array state\n// (tone + toneOffset + per-block last-flagged time) rather than painted into\n// a persistent buffer, so a theme flip or resize repaints every block\n// identically instead of leaving stale pixels from the old theme behind.\n// ---------------------------------------------------------------------------\n\nconst MIN_BLOCK = 12;\nconst MAX_BLOCK = 24;\nconst TICK_MS = 220; // visualized P-frame interval\nconst HOLD_MS = 140; // outline holds at full strength\nconst FADE_MS = 200; // then fades back to unoutlined\nconst FLASH_MS = HOLD_MS + FADE_MS;\nconst FLAG_FRACTION = 0.08; // codec's typical sparse P-frame update ratio\nconst FLAG_MIN = 2;\nconst FLAG_MAX = 4; // capped for legibility — see header note\nconst TONE_AMP = 0.05; // reflecting bound on toneOffset\nconst TONE_STEP = 0.018; // per-flag nudge, before reflection\nconst BASE_ALPHA = 0.05; // narrow luminance band floor\nconst BASE_NOISE = 0.045; // per-block fixed-seed spread, dark theme\nconst LIGHT_NOISE_MULT = 1.6; // widened on a light background — see below\nconst WARMUP_TICKS = 16; // pre-drifts the field so mount isn't a bare I-frame\nconst RM_WARMUP_TICKS = 24;\n\nfunction mulberry32(seed: number) {\n  let a = seed >>> 0;\n  return () => {\n    a = (a + 0x6d2b79f5) >>> 0;\n    let t = a;\n    t = Math.imul(t ^ (t >>> 15), t | 1);\n    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);\n    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n  };\n}\n\nfunction reflect(v: number, amp: number): number {\n  let x = v;\n  if (x > amp) x = 2 * amp - x;\n  else if (x < -amp) x = -2 * amp - x;\n  if (x > amp) x = amp;\n  else if (x < -amp) x = -amp;\n  return x;\n}\n\n/** parses a getComputedStyle \"rgb(r, g, b)\" / \"rgba(r, g, b, a)\" string */\nfunction parseRgb(s: string): [number, number, number] {\n  const m = s.match(/rgba?\\(\\s*([\\d.]+)[,\\s]+([\\d.]+)[,\\s]+([\\d.]+)/);\n  if (!m) return [128, 128, 128];\n  return [Number(m[1]), Number(m[2]), Number(m[3])];\n}\n\nfunction relativeLuminance([r, g, b]: [number, number, number]): number {\n  return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;\n}\n\nexport interface DeltaFrameMacroblockProps {\n  /** extra classes merged onto the rendered canvas element */\n  className?: string;\n}\n\nexport function DeltaFrameMacroblock({ className = \"\" }: DeltaFrameMacroblockProps) {\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n    const ctx = canvas.getContext(\"2d\");\n    if (!ctx) return;\n\n    const reduced = window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n\n    let disposed = false;\n    let tokensRead = false;\n    let fg: [number, number, number] = [128, 128, 128];\n    let bg: [number, number, number] = [255, 255, 255];\n    let lightMult = 1;\n\n    let width = 0;\n    let height = 0;\n    let sized = false;\n    let cols = 0;\n    let rows = 0;\n    let blockW = 0;\n    let blockH = 0;\n    let count = 0;\n\n    let baseTone = new Float32Array(0);\n    let toneOffset = new Float32Array(0);\n    let flagTime = new Float32Array(0);\n    let rng = mulberry32(0x0dfa11e);\n\n    let simNow = 0;\n    let acc = 0;\n    let last = 0;\n    let raf = 0;\n    let visible = true;\n\n    const readTokens = () => {\n      const cs = getComputedStyle(document.documentElement);\n      fg = parseRgb(cs.getPropertyValue(\"--foreground\") || getComputedStyle(canvas).color);\n      bg = parseRgb(cs.getPropertyValue(\"--background\") || \"rgb(255,255,255)\");\n      // light theme washes out subtle per-block variance faster than dark —\n      // widen the fixed-seed noise amplitude, still purely a multiplier on\n      // token-derived alpha, never a new colour.\n      lightMult = relativeLuminance(bg) > 0.5 ? LIGHT_NOISE_MULT : 1;\n      tokensRead = true;\n    };\n\n    const layout = () => {\n      const rect = canvas.getBoundingClientRect();\n      if (rect.width < 2 || rect.height < 2) {\n        sized = false;\n        return;\n      }\n      width = rect.width;\n      height = rect.height;\n      const dpr = Math.min(window.devicePixelRatio || 1, 2);\n      canvas.width = Math.max(1, Math.round(width * dpr));\n      canvas.height = Math.max(1, Math.round(height * dpr));\n      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n\n      const minDim = Math.min(width, height);\n      const block = Math.min(MAX_BLOCK, Math.max(MIN_BLOCK, Math.round(minDim / 16)));\n      cols = Math.max(1, Math.round(width / block));\n      rows = Math.max(1, Math.round(height / block));\n      blockW = width / cols;\n      blockH = height / rows;\n      count = cols * rows;\n\n      rng = mulberry32(0x0dfa11e);\n      baseTone = new Float32Array(count);\n      toneOffset = new Float32Array(count);\n      flagTime = new Float32Array(count).fill(-Infinity);\n      for (let i = 0; i < count; i++) baseTone[i] = rng() * 2 - 1;\n\n      simNow = 0;\n      acc = 0;\n      sized = true;\n    };\n\n    /** one tick: flags a small capped subset of blocks as \"changed\" */\n    const tick = (t: number) => {\n      const n = Math.min(count, Math.max(FLAG_MIN, Math.min(FLAG_MAX, Math.round(count * FLAG_FRACTION))));\n      const chosen = new Set<number>();\n      let guard = 0;\n      while (chosen.size < n && guard < n * 20) {\n        guard++;\n        chosen.add(Math.floor(rng() * count));\n      }\n      for (const i of chosen) {\n        flagTime[i] = t;\n        const step = (rng() - 0.5) * 2 * TONE_STEP;\n        toneOffset[i] = reflect((toneOffset[i] ?? 0) + step, TONE_AMP);\n      }\n    };\n\n    const draw = (t: number) => {\n      if (!sized) return;\n      if (!tokensRead) readTokens();\n\n      ctx.clearRect(0, 0, width, height);\n      ctx.fillStyle = `rgb(${bg[0]}, ${bg[1]}, ${bg[2]})`;\n      ctx.fillRect(0, 0, width, height);\n\n      const fgStyle = `rgb(${fg[0]}, ${fg[1]}, ${fg[2]})`;\n      ctx.fillStyle = fgStyle;\n      for (let r = 0; r < rows; r++) {\n        for (let c = 0; c < cols; c++) {\n          const i = r * cols + c;\n          const noise = (baseTone[i] ?? 0) * BASE_NOISE * lightMult;\n          const drift = toneOffset[i] ?? 0;\n          let alpha = BASE_ALPHA + noise + drift;\n          if (alpha < 0.015) alpha = 0.015;\n          else if (alpha > 0.22) alpha = 0.22;\n          ctx.globalAlpha = alpha;\n          ctx.fillRect(c * blockW, r * blockH, blockW, blockH);\n        }\n      }\n\n      ctx.strokeStyle = fgStyle;\n      ctx.lineWidth = 1;\n      for (let r = 0; r < rows; r++) {\n        for (let c = 0; c < cols; c++) {\n          const i = r * cols + c;\n          const age = t - (flagTime[i] ?? -Infinity);\n          if (age < 0 || age > FLASH_MS) continue;\n          const strength = age <= HOLD_MS ? 1 : 1 - (age - HOLD_MS) / FADE_MS;\n          ctx.globalAlpha = strength;\n          ctx.strokeRect(c * blockW + 0.5, r * blockH + 0.5, blockW - 1, blockH - 1);\n        }\n      }\n      ctx.globalAlpha = 1;\n    };\n\n    const warm = (ticks: number) => {\n      let t = 0;\n      for (let k = 0; k < ticks; k++) {\n        t += TICK_MS;\n        tick(t);\n      }\n      simNow = t;\n    };\n\n    const loop = (now: number) => {\n      raf = 0;\n      if (!visible || !sized) return;\n      if (last === 0) last = now;\n      const dt = Math.min(100, now - last);\n      last = now;\n      simNow += dt;\n      acc += dt;\n      while (acc >= TICK_MS) {\n        acc -= TICK_MS;\n        tick(simNow - acc);\n      }\n      draw(simNow);\n      raf = requestAnimationFrame(loop);\n    };\n\n    const start = () => {\n      layout();\n      if (!sized) return;\n      readTokens();\n      if (reduced) {\n        warm(RM_WARMUP_TICKS);\n        // freeze on the freshly-flagged frame itself — 0ms into the 140ms\n        // hold, before any fade softens which blocks are flagged.\n        draw(simNow);\n        return;\n      }\n      warm(WARMUP_TICKS);\n      last = 0;\n      if (!raf) raf = requestAnimationFrame(loop);\n    };\n\n    let resizeTimer = 0;\n    const onResize = () => {\n      window.clearTimeout(resizeTimer);\n      resizeTimer = window.setTimeout(() => {\n        if (disposed) return;\n        cancelAnimationFrame(raf);\n        raf = 0;\n        start();\n      }, 120);\n    };\n    const ro = new ResizeObserver(onResize);\n    ro.observe(canvas);\n\n    const mo = new MutationObserver(() => {\n      tokensRead = false;\n      if (reduced) draw(simNow);\n    });\n    mo.observe(document.documentElement, { attributes: true, attributeFilter: [\"class\"] });\n\n    const io = new IntersectionObserver((entries) => {\n      visible = entries[0]?.isIntersecting ?? true;\n      if (visible && sized && !reduced && !raf) {\n        last = 0;\n        raf = requestAnimationFrame(loop);\n      }\n    });\n    io.observe(canvas);\n\n    document.fonts.ready.then(() => {\n      if (!disposed) onResize();\n    });\n\n    start();\n\n    return () => {\n      disposed = true;\n      cancelAnimationFrame(raf);\n      raf = 0;\n      window.clearTimeout(resizeTimer);\n      ro.disconnect();\n      mo.disconnect();\n      io.disconnect();\n    };\n  }, []);\n\n  return (\n    <canvas\n      ref={canvasRef}\n      aria-hidden\n      className={`block h-full w-full text-foreground ${className}`}\n    />\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/ui/delta-frame-macroblock.tsx"
    }
  ],
  "meta": {
    "collection": "core",
    "tags": [
      "background",
      "divider",
      "canvas",
      "codec",
      "grid",
      "generative",
      "ambient",
      "monochrome"
    ],
    "instruction": "Build <DeltaFrameMacroblock className?> as a full-bleed <canvas> (block h-full w-full) drawn with Canvas 2D on a direct-DOM rAF loop, no React state on the hot path. GEOMETRY: block = clamp(round(minDim / 16), 12, 24) px where minDim = min(container width, height); cols = round(width / block), rows = round(height / block), then blockW = width / cols and blockH = height / rows so the grid fills the container exactly with no partial edge blocks (a 320px band at 16px blocks holds a 20-block-wide row). PER-BLOCK STATE, held in three Float32Arrays sized cols*rows and mutated in place: baseTone (a fixed per-block seed value in [-1, 1] from a mulberry32 PRNG re-seeded on every layout, drawn once and never reseeded during the session), toneOffset (starts at 0, only ever moved the tick a block is flagged, bounded to +-0.05 with reflection at the bound rather than clamping — an unbounded random walk would eventually saturate the narrow luminance band into salt-and-pepper noise, which both breaks light theme and eventually stops changing, failing the always-different-at-rest rule), and flagTime (the sim-clock timestamp, in ms, a block was last flagged; -Infinity until first flagged). TICK, every 220ms (accumulator-driven off real elapsed time so speed never depends on frame rate, not a raw per-frame animation of a real codec tick rate per the round 9 decoupling rule): pick a small capped subset of distinct block indices — n = clamp(round(count * 0.08), 2, 4), i.e. the spec's 6-10% sparse-update ratio capped to an absolute 2-4 blocks regardless of grid size, because at typical card-scale grids (150-400 blocks) the raw percentage produces 10-30 simultaneous flags which, against a 340ms outline lifetime, overlaps into continuous shimmer rather than discrete countable events (this is a deliberate, spec-authorized departure toward more aggressive/legible timing, not a defect) — set flagTime[i] = current sim time and nudge toneOffset[i] by a random +-0.018 step (reflected) for each chosen block. RENDER, every animation frame, full redraw from the typed arrays (never a persistent buffer, so a theme flip or resize repaints every block identically instead of leaving stale-theme pixels behind): clear to --background, then for every block fill with --foreground at alpha = clamp(0.05 base + baseTone*0.045*lightMult + toneOffset, 0.015, 0.22) — a narrow, low-contrast luminance band, value-only, no hue, where lightMult is 1.6 in light theme and 1 in dark (derived from the resolved --background token's relative luminance, not from string-matching a theme class, so it stays token-driven with zero colour literals). Then, for every block whose (simNow - flagTime) is within 0-340ms, stroke its rect in --foreground at 1px: full alpha (1.0) for the first 140ms (the hold), then linearly fading to 0 over the following 200ms (the fade) — 340ms total, chosen so at most roughly one and a half tick-generations of outlines ever overlap on screen at once, keeping each flash an individually followable event rather than a continuous glow. TOKENS: --foreground and --background are read via getComputedStyle(document.documentElement).getPropertyValue and re-derived on a MutationObserver watching documentElement's class attribute; a tokensRead guard means draw() never paints before the first successful read, checked explicitly on the rAF start, the ResizeObserver-triggered relayout, and the IntersectionObserver resume path. Canvas is DPR-clamped to 2 and resized via ResizeObserver (120ms debounce) which fully relays out the grid, reseeds baseTone, and resets toneOffset/flagTime — a resize is treated as a fresh mount, not a stretch of stale state. WARM START (both paths run a synchronous tick loop before any paint, so mount never shows a bare freshly-reset I-frame): normal path runs 16 synchronous 220ms ticks before starting the rAF loop, so the very first painted frame already shows a natural scatter of blocks at different points in their fade lifecycle (t0 = a scatter of freshly-flagged blocks mid-fade from a recent tick, static base grid otherwise unchanged since mount) plus pre-drifted toneOffset variance from earlier warm-up ticks. prefers-reduced-motion: reduce runs 24 synchronous ticks, draws exactly one frame at that exact final sim time (0ms into the last tick's 140ms hold, full-strength outlines, before any fade softens which blocks are flagged versus static — the most legible single frame of the mechanic), and binds no rAF and no further ticks. Pauses on IntersectionObserver exit, resumes with a reset last-frame timestamp on re-entry. No interaction: the flag pattern is never linked to pointer position — the mechanic is the codec's own change-detection decision, not a hover-responsive grid, and a pointer-linked flag pattern would also risk mixing accent into what must stay a value-only highlight. Props: className only. Zero dependencies, canvas + CSS token reads only, every colour --foreground/--background, no --ns-accent, no --border used as a fill or stroke."
  },
  "type": "registry:ui"
}