Skip to main content

ns-ui

Delta Frame Macroblock

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.

Use when Pick delta-frame-macroblock for a quiet band/divider whose subject IS a change-detection decision — a mostly static block grid where a small, individually-countable subset of blocks flashes as 'changed' each tick and everything else is provably left alone, sourced from real MPEG/H.26x P-frame skip-flag encoding. background-ascii-dither is the better pick when the field should read as a continuous glyph-mapped luminance surface driven by an image or flowing noise rather than discrete per-cell change events; background-truchet-weave when the surface should read as one continuous interlaced line weave rather than a static cell grid with sparse redraws.

Install

npx shadcn add https://design.helpmarq.com/r/delta-frame-macroblock.json

Ask AI

Point an assistant at this component's docs (llms-full.txt) with one click.

Claude, ChatGPT, Grok, and Perplexity open with the prompt already in. Gemini copies it to your clipboard first. Paste it in once the chat opens.

Source
registry/core/delta-frame-macroblock/component.tsx
"use client";

import { useEffect, useRef } from "react";

// ---------------------------------------------------------------------------
// DeltaFrameMacroblock — a quiet full-width band that visualizes a video
// codec's own P-frame change-detection decision, not the pixels it redraws.
//
// After an MPEG/H.26x-class codec sends one full (I-)frame, every frame after
// that only encodes the macroblocks that actually changed; each block carries
// a "redraw" or "skip, unchanged" flag, and a decoder leaves every skipped
// block's pixels exactly as they were in the frame buffer. Scrub a real
// codec's per-block change map and you see a sparse, shifting set of flagged
// blocks against a mostly static field — the compression artifact visible as
// blockiness in low-bitrate video, made deliberate here instead of a defect.
//
// A uniform grid of macroblocks is sized off the container's SMALLER
// dimension: block = clamp(round(minDim / 16), 12, 24) px, then cols/rows are
// rounded so the grid fills the container exactly (a 320px band at 16px
// blocks holds a 20-block-wide row). Each block carries a fixed per-block
// seed tone (drawn once, never reseeded) plus a bounded, reflecting
// toneOffset that only moves the tick a block is flagged — that is the
// "slightly shifted tone" of a motion-compensated redraw, and reflecting the
// offset at its amplitude ceiling keeps the drift visible forever without
// ever saturating into salt-and-pepper noise (an unbounded random walk would
// eventually finish, which fails the always-different-at-rest rule).
//
// A fixed 220ms tick — the visualized P-frame interval, deliberately slower
// than a real ~30fps codec tick per the round 9 decoupling rule, so each
// delta event stays an individually followable discrete flash rather than a
// strobe — flags a SMALL, capped number of blocks as "changed" (2-4 blocks
// regardless of grid size, not the raw 6-10% the source ratio would suggest:
// at typical card-scale grids of 150-400 blocks, 6-10% is 10-30 simultaneous
// flags, and against a 340ms outline lifetime that overlaps into a
// continuous shimmer rather than discrete countable events — the spec's own
// kill criteria pre-authorizes making the timing more aggressive rather than
// shipping that). A flagged block's outline appears instantly, holds at full
// --foreground-weight stroke for 140ms, then fades over the next 200ms back
// to unoutlined — 340ms total, comfortably inside the 220ms-spaced ticks so
// at most two flash generations ever overlap.
//
// The whole board is redrawn every animation frame from typed-array state
// (tone + toneOffset + per-block last-flagged time) rather than painted into
// a persistent buffer, so a theme flip or resize repaints every block
// identically instead of leaving stale pixels from the old theme behind.
// ---------------------------------------------------------------------------

const MIN_BLOCK = 12;
const MAX_BLOCK = 24;
const TICK_MS = 220; // visualized P-frame interval
const HOLD_MS = 140; // outline holds at full strength
const FADE_MS = 200; // then fades back to unoutlined
const FLASH_MS = HOLD_MS + FADE_MS;
const FLAG_FRACTION = 0.08; // codec's typical sparse P-frame update ratio
const FLAG_MIN = 2;
const FLAG_MAX = 4; // capped for legibility — see header note
const TONE_AMP = 0.05; // reflecting bound on toneOffset
const TONE_STEP = 0.018; // per-flag nudge, before reflection
const BASE_ALPHA = 0.05; // narrow luminance band floor
const BASE_NOISE = 0.045; // per-block fixed-seed spread, dark theme
const LIGHT_NOISE_MULT = 1.6; // widened on a light background — see below
const WARMUP_TICKS = 16; // pre-drifts the field so mount isn't a bare I-frame
const RM_WARMUP_TICKS = 24;

function mulberry32(seed: number) {
  let a = seed >>> 0;
  return () => {
    a = (a + 0x6d2b79f5) >>> 0;
    let t = a;
    t = Math.imul(t ^ (t >>> 15), t | 1);
    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

function reflect(v: number, amp: number): number {
  let x = v;
  if (x > amp) x = 2 * amp - x;
  else if (x < -amp) x = -2 * amp - x;
  if (x > amp) x = amp;
  else if (x < -amp) x = -amp;
  return x;
}

/** parses a getComputedStyle "rgb(r, g, b)" / "rgba(r, g, b, a)" string */
function parseRgb(s: string): [number, number, number] {
  const m = s.match(/rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)/);
  if (!m) return [128, 128, 128];
  return [Number(m[1]), Number(m[2]), Number(m[3])];
}

function relativeLuminance([r, g, b]: [number, number, number]): number {
  return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;
}

export interface DeltaFrameMacroblockProps {
  /** extra classes merged onto the rendered canvas element */
  className?: string;
}

export function DeltaFrameMacroblock({ className = "" }: DeltaFrameMacroblockProps) {
  const canvasRef = useRef<HTMLCanvasElement>(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    if (!ctx) return;

    const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

    let disposed = false;
    let tokensRead = false;
    let fg: [number, number, number] = [128, 128, 128];
    let bg: [number, number, number] = [255, 255, 255];
    let lightMult = 1;

    let width = 0;
    let height = 0;
    let sized = false;
    let cols = 0;
    let rows = 0;
    let blockW = 0;
    let blockH = 0;
    let count = 0;

    let baseTone = new Float32Array(0);
    let toneOffset = new Float32Array(0);
    let flagTime = new Float32Array(0);
    let rng = mulberry32(0x0dfa11e);

    let simNow = 0;
    let acc = 0;
    let last = 0;
    let raf = 0;
    let visible = true;

    const readTokens = () => {
      const cs = getComputedStyle(document.documentElement);
      fg = parseRgb(cs.getPropertyValue("--foreground") || getComputedStyle(canvas).color);
      bg = parseRgb(cs.getPropertyValue("--background") || "rgb(255,255,255)");
      // light theme washes out subtle per-block variance faster than dark —
      // widen the fixed-seed noise amplitude, still purely a multiplier on
      // token-derived alpha, never a new colour.
      lightMult = relativeLuminance(bg) > 0.5 ? LIGHT_NOISE_MULT : 1;
      tokensRead = true;
    };

    const layout = () => {
      const rect = canvas.getBoundingClientRect();
      if (rect.width < 2 || rect.height < 2) {
        sized = false;
        return;
      }
      width = rect.width;
      height = rect.height;
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      canvas.width = Math.max(1, Math.round(width * dpr));
      canvas.height = Math.max(1, Math.round(height * dpr));
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);

      const minDim = Math.min(width, height);
      const block = Math.min(MAX_BLOCK, Math.max(MIN_BLOCK, Math.round(minDim / 16)));
      cols = Math.max(1, Math.round(width / block));
      rows = Math.max(1, Math.round(height / block));
      blockW = width / cols;
      blockH = height / rows;
      count = cols * rows;

      rng = mulberry32(0x0dfa11e);
      baseTone = new Float32Array(count);
      toneOffset = new Float32Array(count);
      flagTime = new Float32Array(count).fill(-Infinity);
      for (let i = 0; i < count; i++) baseTone[i] = rng() * 2 - 1;

      simNow = 0;
      acc = 0;
      sized = true;
    };

    /** one tick: flags a small capped subset of blocks as "changed" */
    const tick = (t: number) => {
      const n = Math.min(count, Math.max(FLAG_MIN, Math.min(FLAG_MAX, Math.round(count * FLAG_FRACTION))));
      const chosen = new Set<number>();
      let guard = 0;
      while (chosen.size < n && guard < n * 20) {
        guard++;
        chosen.add(Math.floor(rng() * count));
      }
      for (const i of chosen) {
        flagTime[i] = t;
        const step = (rng() - 0.5) * 2 * TONE_STEP;
        toneOffset[i] = reflect((toneOffset[i] ?? 0) + step, TONE_AMP);
      }
    };

    const draw = (t: number) => {
      if (!sized) return;
      if (!tokensRead) readTokens();

      ctx.clearRect(0, 0, width, height);
      ctx.fillStyle = `rgb(${bg[0]}, ${bg[1]}, ${bg[2]})`;
      ctx.fillRect(0, 0, width, height);

      const fgStyle = `rgb(${fg[0]}, ${fg[1]}, ${fg[2]})`;
      ctx.fillStyle = fgStyle;
      for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
          const i = r * cols + c;
          const noise = (baseTone[i] ?? 0) * BASE_NOISE * lightMult;
          const drift = toneOffset[i] ?? 0;
          let alpha = BASE_ALPHA + noise + drift;
          if (alpha < 0.015) alpha = 0.015;
          else if (alpha > 0.22) alpha = 0.22;
          ctx.globalAlpha = alpha;
          ctx.fillRect(c * blockW, r * blockH, blockW, blockH);
        }
      }

      ctx.strokeStyle = fgStyle;
      ctx.lineWidth = 1;
      for (let r = 0; r < rows; r++) {
        for (let c = 0; c < cols; c++) {
          const i = r * cols + c;
          const age = t - (flagTime[i] ?? -Infinity);
          if (age < 0 || age > FLASH_MS) continue;
          const strength = age <= HOLD_MS ? 1 : 1 - (age - HOLD_MS) / FADE_MS;
          ctx.globalAlpha = strength;
          ctx.strokeRect(c * blockW + 0.5, r * blockH + 0.5, blockW - 1, blockH - 1);
        }
      }
      ctx.globalAlpha = 1;
    };

    const warm = (ticks: number) => {
      let t = 0;
      for (let k = 0; k < ticks; k++) {
        t += TICK_MS;
        tick(t);
      }
      simNow = t;
    };

    const loop = (now: number) => {
      raf = 0;
      if (!visible || !sized) return;
      if (last === 0) last = now;
      const dt = Math.min(100, now - last);
      last = now;
      simNow += dt;
      acc += dt;
      while (acc >= TICK_MS) {
        acc -= TICK_MS;
        tick(simNow - acc);
      }
      draw(simNow);
      raf = requestAnimationFrame(loop);
    };

    const start = () => {
      layout();
      if (!sized) return;
      readTokens();
      if (reduced) {
        warm(RM_WARMUP_TICKS);
        // freeze on the freshly-flagged frame itself — 0ms into the 140ms
        // hold, before any fade softens which blocks are flagged.
        draw(simNow);
        return;
      }
      warm(WARMUP_TICKS);
      last = 0;
      if (!raf) raf = requestAnimationFrame(loop);
    };

    let resizeTimer = 0;
    const onResize = () => {
      window.clearTimeout(resizeTimer);
      resizeTimer = window.setTimeout(() => {
        if (disposed) return;
        cancelAnimationFrame(raf);
        raf = 0;
        start();
      }, 120);
    };
    const ro = new ResizeObserver(onResize);
    ro.observe(canvas);

    const mo = new MutationObserver(() => {
      tokensRead = false;
      if (reduced) draw(simNow);
    });
    mo.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] });

    const io = new IntersectionObserver((entries) => {
      visible = entries[0]?.isIntersecting ?? true;
      if (visible && sized && !reduced && !raf) {
        last = 0;
        raf = requestAnimationFrame(loop);
      }
    });
    io.observe(canvas);

    document.fonts.ready.then(() => {
      if (!disposed) onResize();
    });

    start();

    return () => {
      disposed = true;
      cancelAnimationFrame(raf);
      raf = 0;
      window.clearTimeout(resizeTimer);
      ro.disconnect();
      mo.disconnect();
      io.disconnect();
    };
  }, []);

  return (
    <canvas
      ref={canvasRef}
      aria-hidden
      className={`block h-full w-full text-foreground ${className}`}
    />
  );
}
Build spec

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.

Props

PropTypeDefaultDescription
className?stringextra classes merged onto the rendered canvas element