Table Heat Shimmer

Table

Dense ops table where rows above a heat threshold shimmer like air over asphalt — live DOM text refracts through an animated SVG displacement filter while cold rows sit dead-still behind hairline borders.

Install
npx shadcn add https://design.helpmarq.com/r/table-heat-shimmer.json
Source
registry/core/table-heat-shimmer/component.tsx
"use client";

import {
  useEffect,
  useId,
  useMemo,
  useRef,
  useState,
  type CSSProperties,
  type FocusEvent,
} from "react";

// ---------------------------------------------------------------------------
// HeatwaveLedger — dense ops table where rows above a heat threshold shimmer
// like air over asphalt: real DOM text refracts through an animated SVG
// feTurbulence → feDisplacementMap filter (one filter per hot-row slot, unique
// per instance), while cold rows sit dead-still behind hairline borders. A
// pointer-events-none overlay canvas adds per-hot-row edge haze strips, fully
// cleared and redrawn each frame. The hot/cold contrast IS the piece — warmth
// is implied by motion, never by hue. One shared rAF walks only the hot list;
// hover/focus on a hot row snaps its displacement to 0 for legibility.
// ---------------------------------------------------------------------------

export type LedgerRow = {
  id: string;
  /** service name */
  service: string;
  /** deploy region */
  region: string;
  /** requests per second */
  reqs: number;
  /** p95 latency in ms */
  p95: number;
  /** heat score 0–100 — rows at/above `threshold` shimmer */
  heat: number;
};

type SortKey = "service" | "region" | "reqs" | "p95" | "heat";
type SortDir = "asc" | "desc";

type Vec3 = [number, number, number];

function parseColor(raw: string): Vec3 | null {
  const s = raw.trim();
  if (s.startsWith("#")) {
    const hex = s.slice(1);
    if (hex.length === 3) {
      const r = parseInt(hex.slice(0, 1) + hex.slice(0, 1), 16);
      const g = parseInt(hex.slice(1, 2) + hex.slice(1, 2), 16);
      const b = parseInt(hex.slice(2, 3) + hex.slice(2, 3), 16);
      return Number.isNaN(r + g + b) ? null : [r, g, b];
    }
    if (hex.length >= 6) {
      const r = parseInt(hex.slice(0, 2), 16);
      const g = parseInt(hex.slice(2, 4), 16);
      const b = parseInt(hex.slice(4, 6), 16);
      return Number.isNaN(r + g + b) ? null : [r, g, b];
    }
    return null;
  }
  const m = s.match(/rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)/);
  return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
}

function mix(a: Vec3, b: Vec3, t: number): Vec3 {
  return [
    Math.round(a[0] + (b[0] - a[0]) * t),
    Math.round(a[1] + (b[1] - a[1]) * t),
    Math.round(a[2] + (b[2] - a[2]) * t),
  ];
}

const DEFAULT_ROWS: LedgerRow[] = [
  { id: "edge-gateway", service: "edge-gateway", region: "iad-1", reqs: 12480, p95: 811, heat: 92 },
  { id: "auth-service", service: "auth-service", region: "fra-1", reqs: 8921, p95: 640, heat: 84 },
  { id: "search-index", service: "search-index", region: "sfo-2", reqs: 6115, p95: 505, heat: 76 },
  { id: "billing-api", service: "billing-api", region: "iad-1", reqs: 2210, p95: 189, heat: 48 },
  { id: "media-transcode", service: "media-transcode", region: "ams-1", reqs: 940, p95: 152, heat: 41 },
  { id: "notifications", service: "notifications", region: "sfo-2", reqs: 3308, p95: 96, heat: 34 },
  { id: "feature-flags", service: "feature-flags", region: "fra-1", reqs: 5170, p95: 22, heat: 21 },
  { id: "audit-log", service: "audit-log", region: "iad-2", reqs: 1204, p95: 64, heat: 18 },
  { id: "static-assets", service: "static-assets", region: "global", reqs: 22930, p95: 12, heat: 11 },
  { id: "cron-runner", service: "cron-runner", region: "ams-1", reqs: 88, p95: 45, heat: 7 },
];

const COLUMNS: { key: SortKey; label: string; numeric: boolean }[] = [
  { key: "service", label: "Service", numeric: false },
  { key: "region", label: "Region", numeric: false },
  { key: "reqs", label: "Req/s", numeric: true },
  { key: "p95", label: "p95 ms", numeric: true },
  { key: "heat", label: "Heat", numeric: true },
];

// shimmer numbers
const PERIOD_S = 2.2; // per-row sine period
const PHASE_S = 0.4; // phase offset per hot-row index
const SCALE_MIN = 1.5; // px displacement floor
const SCALE_MAX = 3.0; // px displacement ceiling
const RESEED_MS = 4000; // turbulence seed re-randomize interval
const ATTR_MS = 1000 / 30; // SVG attribute writes throttled to 30fps
const FREEZE_IN_MS = 120; // hover legibility snap
const FREEZE_OUT_MS = 400; // ease back on leave
const FREEZE_DEADLINE_MS = 500; // forced completion — no stranded mid-wobble
const HAZE_H = 24; // gradient strip height in px
const HAZE_ALPHA = 0.08;

type FreezeState = { v: number; from: number; target: number; start: number };

export function HeatwaveLedger({
  rows = DEFAULT_ROWS,
  threshold = 70,
  title = "Server load — last 24h",
  timestamp = "updated 09:41:07 UTC",
  className = "",
}: {
  /** table data — rows with heat >= threshold shimmer */
  rows?: LedgerRow[];
  /** heat score at/above which a row is hot */
  threshold?: number;
  /** card title */
  title?: string;
  /** mono timestamp shown beside the title */
  timestamp?: string;
  className?: string;
}) {
  // unique, hydration-stable filter id prefix (useId sanitized for url(#…))
  const rawId = useId();
  const uid = useMemo(() => "heat" + rawId.replace(/[^a-zA-Z0-9_-]/g, ""), [rawId]);

  const [sortKey, setSortKey] = useState<SortKey>("heat");
  const [sortDir, setSortDir] = useState<SortDir>("desc");
  const [selected, setSelected] = useState<ReadonlySet<string>>(() => new Set());
  const [reduced, setReduced] = useState(false);
  // mirrors row hover into a data-attribute alongside the CSS `hover:` classes
  // below — Chromium never advances `:hover` for the site's synthetic
  // (isTrusted: false) autoplay driver, so the cosmetic hover tint needs a JS
  // source of truth too (same pattern as button-glass).
  const [hoveredId, setHoveredId] = useState<string | null>(null);

  const wrapRef = useRef<HTMLDivElement>(null);
  const tableRef = useRef<HTMLTableElement>(null);
  const tbodyRef = useRef<HTMLTableSectionElement>(null);
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const rowEls = useRef(new Map<string, HTMLTableRowElement>());
  const dispEls = useRef<(SVGFEDisplacementMapElement | null)[]>([]);
  const turbEls = useRef<(SVGFETurbulenceElement | null)[]>([]);
  const freezeRef = useRef(new Map<string, FreezeState>());
  const wakeRef = useRef<(() => void) | null>(null);

  const sorted = useMemo(() => {
    const arr = [...rows];
    arr.sort((a, b) => {
      const c =
        sortKey === "service" || sortKey === "region"
          ? a[sortKey].localeCompare(b[sortKey])
          : a[sortKey] - b[sortKey];
      return sortDir === "asc" ? c : -c;
    });
    return arr;
  }, [rows, sortKey, sortDir]);

  // hot rows in sorted order — index drives the shimmer phase, so sorting
  // (by heat or anything else) re-phases the field
  const hot = useMemo(
    () =>
      sorted
        .map((r, i) => ({ id: r.id, index: i }))
        .filter((_, i) => (sorted[i]?.heat ?? 0) >= threshold),
    [sorted, threshold]
  );
  const hotSlotById = useMemo(
    () => new Map(hot.map((h, k) => [h.id, k])),
    [hot]
  );
  const hotSig = useMemo(
    () => hot.map((h) => `${h.id}:${h.index}`).join("|"),
    [hot]
  );

  // reduced motion: no filter, no canvas — static accent rule instead
  useEffect(() => {
    const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
    setReduced(mq.matches);
    const onChange = () => setReduced(mq.matches);
    mq.addEventListener("change", onChange);
    return () => mq.removeEventListener("change", onChange);
  }, []);

  // -- hover/focus freeze: eases a hot row's displacement to 0 for legibility
  const setFreeze = (id: string, target: number) => {
    const m = freezeRef.current;
    const s = m.get(id) ?? { v: 0, from: 0, target: 0, start: 0 };
    if (s.target === target) return;
    s.from = s.v;
    s.target = target;
    s.start = performance.now();
    m.set(id, s);
    wakeRef.current?.();
  };

  // -- shared rAF: walks ONLY the hot list ---------------------------------
  useEffect(() => {
    if (reduced || hot.length === 0) return;
    const wrap = wrapRef.current;
    const table = tableRef.current;
    const tbody = tbodyRef.current;
    const canvas = canvasRef.current;
    if (!wrap || !table || !tbody || !canvas) return;
    const ctx = canvas.getContext("2d");
    if (!ctx) return;

    const hotList = hot; // captured — effect re-runs when hotSig changes

    // haze ink: --accent mixed 20% into --muted, derived at mount and
    // re-derived live on documentElement class flips
    let haze: Vec3 = [114, 121, 145];
    const derive = () => {
      const cs = getComputedStyle(document.documentElement);
      const muted = parseColor(cs.getPropertyValue("--muted")) ?? [143, 143, 143];
      const accent = parseColor(cs.getPropertyValue("--accent")) ?? [0, 107, 255];
      haze = mix(muted, accent, 0.2);
    };
    derive();

    // overlay canvas is a replaced element — explicit style.width/height,
    // backing store scaled by dpr
    let w = 0;
    let h = 0;
    let dpr = 1;
    let sized = false;
    const resize = () => {
      const rect = wrap.getBoundingClientRect();
      if (rect.width < 2 || rect.height < 2) {
        sized = false; // zero-size guard — RO wakes us when real
        return;
      }
      w = rect.width;
      h = rect.height;
      dpr = Math.min(2, window.devicePixelRatio || 1);
      canvas.width = Math.max(1, Math.round(w * dpr));
      canvas.height = Math.max(1, Math.round(h * dpr));
      canvas.style.width = `${w}px`;
      canvas.style.height = `${h}px`;
      sized = true;
    };
    resize();

    let raf = 0;
    let visible = true;
    let lastAttr = 0;
    let lastSeed = 0;

    const clearCanvas = () => {
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.clearRect(0, 0, canvas.width, canvas.height);
    };

    const allFrozen = () =>
      hotList.every((hr) => {
        const s = freezeRef.current.get(hr.id);
        return s !== undefined && s.target === 1 && s.v >= 0.999;
      });

    const strip = (yFrom: number, yTo: number, a: number) => {
      const g = ctx.createLinearGradient(0, yFrom, 0, yTo);
      const [r, gg, b] = haze;
      g.addColorStop(0, `rgba(${r},${gg},${b},${a})`);
      g.addColorStop(1, `rgba(${r},${gg},${b},0)`);
      ctx.fillStyle = g;
      ctx.fillRect(0, Math.min(yFrom, yTo), w, Math.abs(yTo - yFrom));
    };

    const step = (now: number) => {
      raf = 0;
      if (!visible) return; // paused offscreen — IO wakes us

      // advance freeze transitions (ease-out cubic, hard deadline at 500ms
      // so a fast hover-scrub can never strand a row mid-wobble)
      for (const hr of hotList) {
        const s = freezeRef.current.get(hr.id);
        if (!s || s.v === s.target) continue;
        const el = now - s.start;
        const dur = s.target === 1 ? FREEZE_IN_MS : FREEZE_OUT_MS;
        if (el >= dur || el >= FREEZE_DEADLINE_MS) {
          s.v = s.target;
        } else {
          const p = el / dur;
          const e = 1 - Math.pow(1 - p, 3);
          s.v = s.from + (s.target - s.from) * e;
        }
      }

      const writeAttrs = now - lastAttr >= ATTR_MS;
      if (writeAttrs) lastAttr = now;
      // reseed rides an attribute-write frame so the throttle can't drop it
      const reseed = writeAttrs && now - lastSeed >= RESEED_MS;
      if (reseed) lastSeed = now;

      // haze canvas: fully cleared and redrawn every frame — no accumulation
      const tbodyH = tbody.offsetHeight;
      const drawable = sized && tbodyH >= 2;
      if (sized) clearCanvas();
      if (drawable) {
        ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
        ctx.save();
        // clip to the tbody band — haze never bleeds into thead/footer
        const clipTop = table.offsetTop + tbody.offsetTop;
        ctx.beginPath();
        ctx.rect(0, clipTop, w, tbodyH);
        ctx.clip();
      }

      const t = now / 1000;
      for (let k = 0; k < hotList.length; k++) {
        const hr = hotList[k];
        if (!hr) continue;
        const fr = freezeRef.current.get(hr.id)?.v ?? 0;
        const osc =
          0.5 + 0.5 * Math.sin(((t - hr.index * PHASE_S) / PERIOD_S) * Math.PI * 2);
        const scale = (SCALE_MIN + (SCALE_MAX - SCALE_MIN) * osc) * (1 - fr);

        if (writeAttrs) {
          dispEls.current[k]?.setAttribute("scale", scale.toFixed(2));
          if (reseed)
            turbEls.current[k]?.setAttribute(
              "seed",
              String(Math.floor(Math.random() * 1000))
            );
        }

        if (drawable && fr < 0.999) {
          const tr = rowEls.current.get(hr.id);
          if (tr) {
            // offset coords within the wrapper (tr.offsetParent is the table,
            // table's offsetParent is the relative wrapper) — never page-absolute
            const top = table.offsetTop + tr.offsetTop;
            const bottom = top + tr.offsetHeight;
            const a = HAZE_ALPHA * (0.55 + 0.45 * osc) * (1 - fr);
            strip(top, top - HAZE_H, a); // rises off the top edge
            strip(bottom, bottom + HAZE_H, a); // sinks off the bottom edge
          }
        }
      }
      if (drawable) ctx.restore();

      // sleep: every hot row hover-frozen and settled — displacement is 0,
      // haze is 0; pin scales to exactly 0 and stop the loop entirely
      if (allFrozen()) {
        for (let k = 0; k < hotList.length; k++)
          dispEls.current[k]?.setAttribute("scale", "0");
        if (sized) clearCanvas();
        return;
      }
      raf = requestAnimationFrame(step);
    };

    const wake = () => {
      if (!raf && visible) raf = requestAnimationFrame(step);
    };
    wakeRef.current = wake;
    raf = requestAnimationFrame(step);

    const io = new IntersectionObserver((entries) => {
      visible = entries[0]?.isIntersecting ?? true;
      if (visible) wake();
    });
    io.observe(wrap);

    const ro = new ResizeObserver(() => {
      resize();
      wake();
    });
    ro.observe(wrap);

    const mo = new MutationObserver(() => {
      derive();
      wake();
    });
    mo.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"],
    });

    return () => {
      cancelAnimationFrame(raf);
      raf = 0;
      wakeRef.current = null;
      io.disconnect();
      ro.disconnect();
      mo.disconnect();
      clearCanvas();
    };
  }, [reduced, hotSig, hot]);

  const onSort = (key: SortKey) => {
    if (key === sortKey) {
      setSortDir((d) => (d === "asc" ? "desc" : "asc"));
    } else {
      setSortKey(key);
      setSortDir(key === "service" || key === "region" ? "asc" : "desc");
    }
  };

  const toggleSelect = (id: string) => {
    setSelected((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  };

  const onRowBlur = (id: string) => (e: FocusEvent<HTMLTableRowElement>) => {
    // focus-within: stay frozen while focus moves inside the row
    if (e.currentTarget.contains(e.relatedTarget as Node | null)) return;
    setFreeze(id, 0);
  };

  return (
    <div
      className={`overflow-hidden rounded-md border border-border bg-surface ${className}`}
    >
      <div className="flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1 px-5 pb-4 pt-5">
        <h2 className="text-sm font-semibold tracking-tight text-foreground">
          {title}
        </h2>
        <span className="font-mono text-[11px] tracking-wider text-muted">
          {timestamp}
        </span>
      </div>

      <div ref={wrapRef} className="relative">
        {/* one filter per hot-row slot, ids unique per instance */}
        {!reduced && hot.length > 0 && (
          <svg aria-hidden width="0" height="0" className="absolute" focusable="false">
            <defs>
              {hot.map((h, k) => (
                <filter
                  key={h.id}
                  id={`${uid}-h${k}`}
                  x="-2%"
                  y="-60%"
                  width="104%"
                  height="220%"
                  colorInterpolationFilters="sRGB"
                >
                  <feTurbulence
                    ref={(el) => {
                      turbEls.current[k] = el;
                    }}
                    type="fractalNoise"
                    baseFrequency="0.008 0.02"
                    numOctaves={2}
                    seed={7 + k * 13}
                    result="n"
                  />
                  <feDisplacementMap
                    ref={(el) => {
                      dispEls.current[k] = el;
                    }}
                    in="SourceGraphic"
                    in2="n"
                    scale="0"
                    xChannelSelector="R"
                    yChannelSelector="G"
                  />
                </filter>
              ))}
            </defs>
          </svg>
        )}

        <table ref={tableRef} className="w-full border-collapse text-sm">
          <thead>
            <tr className="border-b border-border">
              <th scope="col" className="w-10 px-5 py-2.5">
                <span className="sr-only">Select</span>
              </th>
              {COLUMNS.map((col) => {
                const active = sortKey === col.key;
                return (
                  <th
                    key={col.key}
                    scope="col"
                    aria-sort={
                      active
                        ? sortDir === "asc"
                          ? "ascending"
                          : "descending"
                        : undefined
                    }
                    className={`px-3 py-2.5 first:pl-5 last:pr-5 ${
                      col.numeric ? "text-right" : "text-left"
                    }`}
                  >
                    <button
                      type="button"
                      onClick={() => onSort(col.key)}
                      className={`inline-flex items-center gap-1 rounded-sm font-mono text-[11px] uppercase tracking-widest transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent ${
                        active ? "text-foreground" : "text-muted hover:text-foreground"
                      } ${col.numeric ? "flex-row-reverse" : ""}`}
                    >
                      {col.label}
                      <svg
                        viewBox="0 0 8 8"
                        aria-hidden
                        className={`h-2 w-2 transition-opacity duration-150 ${
                          active ? "opacity-100" : "opacity-0"
                        } ${active && sortDir === "asc" ? "rotate-180" : ""}`}
                        fill="currentColor"
                      >
                        <path d="M4 6 0.8 2h6.4Z" />
                      </svg>
                    </button>
                  </th>
                );
              })}
            </tr>
          </thead>
          <tbody ref={tbodyRef}>
            {sorted.map((row) => {
              const slot = hotSlotById.get(row.id);
              const isHot = slot !== undefined;
              const isSel = selected.has(row.id);

              const style: CSSProperties = {};
              if (isHot && !reduced) style.filter = `url(#${uid}-h${slot})`;
              const shadows: string[] = [];
              if (isHot && reduced)
                shadows.push("inset 2px 0 0 0 var(--accent)"); // static heat rule
              if (isSel)
                shadows.push(
                  "inset 0 0 0 1px color-mix(in srgb, var(--foreground) 35%, var(--muted))"
                );
              if (shadows.length > 0) style.boxShadow = shadows.join(", ");

              // token-relative fills only: hover raises one surface step,
              // selection sits one step above that
              // (`data-[hover=true]` mirrors `:hover` for synthetic/driver input)
              const bg = isSel
                ? "bg-foreground/[0.06]"
                : isHot && reduced
                  ? "bg-foreground/[0.04] hover:bg-foreground/[0.06] data-[hover=true]:bg-foreground/[0.06]"
                  : "hover:bg-foreground/[0.04] data-[hover=true]:bg-foreground/[0.04]";

              return (
                <tr
                  key={row.id}
                  ref={(el) => {
                    if (el) rowEls.current.set(row.id, el);
                    else rowEls.current.delete(row.id);
                  }}
                  style={style}
                  aria-selected={isSel}
                  data-hover={hoveredId === row.id}
                  onPointerEnter={() => {
                    setHoveredId(row.id);
                    if (isHot && !reduced) setFreeze(row.id, 1);
                  }}
                  onPointerLeave={() => {
                    setHoveredId((h) => (h === row.id ? null : h));
                    if (isHot && !reduced) setFreeze(row.id, 0);
                  }}
                  onFocus={isHot && !reduced ? () => setFreeze(row.id, 1) : undefined}
                  onBlur={isHot && !reduced ? onRowBlur(row.id) : undefined}
                  className={`border-b border-border transition-colors duration-150 last:border-b-0 ${bg}`}
                >
                  <td className="px-5 py-3">
                    <input
                      type="checkbox"
                      checked={isSel}
                      onChange={() => toggleSelect(row.id)}
                      aria-label={`Select ${row.service}`}
                      className="block h-3.5 w-3.5 cursor-pointer"
                      style={{ accentColor: "var(--accent)" }}
                    />
                  </td>
                  <td className="px-3 py-3 font-medium text-foreground">
                    {row.service}
                  </td>
                  <td className="px-3 py-3 font-mono text-xs tracking-wide text-muted">
                    {row.region}
                  </td>
                  <td className="px-3 py-3 text-right font-mono text-xs tabular-nums text-muted">
                    {row.reqs.toLocaleString("en-US")}
                  </td>
                  <td className="px-3 py-3 text-right font-mono text-xs tabular-nums text-muted">
                    {row.p95.toLocaleString("en-US")}
                  </td>
                  <td
                    className={`px-3 py-3 pr-5 text-right font-mono text-xs tabular-nums ${
                      isHot ? "font-medium text-foreground" : "text-muted"
                    }`}
                  >
                    {row.heat}
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>

        {/* edge-haze overlay — explicit CSS size set in the resize handler */}
        {!reduced && hot.length > 0 && (
          <canvas
            ref={canvasRef}
            aria-hidden
            className="pointer-events-none absolute left-0 top-0"
          />
        )}
      </div>

      <div className="flex flex-wrap items-center justify-between gap-x-4 gap-y-2 border-t border-border px-5 py-3">
        <span className="font-mono text-[11px] tracking-wider text-muted">
          {sorted.length} services · {hot.length} above threshold · {selected.size}{" "}
          selected
        </span>
        <div className="flex items-center gap-2">
          <button
            type="button"
            disabled
            className="rounded-sm border border-border px-2.5 py-1 font-mono text-[11px] text-muted opacity-50 transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
          >
            Prev
          </button>
          <span className="font-mono text-[11px] tabular-nums text-muted">1 / 1</span>
          <button
            type="button"
            disabled
            className="rounded-sm border border-border px-2.5 py-1 font-mono text-[11px] text-muted opacity-50 transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
          >
            Next
          </button>
        </div>
      </div>
    </div>
  );
}
Build spec

Dense data table where rows above a heat threshold visibly shimmer like air over asphalt: real content refracts via an animated SVG displacement filter while cold rows sit dead-still with 1px hairline borders — the hot/cold contrast IS the piece; warmth is implied by motion, never by hue (no orange anywhere). RENDERING: semantic DOM <table> (sortable headers with real client-side comparators, selectable checkbox rows) inside a padded rounded-md surface card, plus (a) one inline SVG <filter> per hot-row slot with instance-unique ids (useId): feTurbulence type=fractalNoise baseFrequency='0.008 0.02' numOctaves=2 feeding feDisplacementMap; each hot <tr> gets style.filter=url(#heat-<id>) so its actual text/cells refract; and (b) a pointer-events:none overlay canvas over the tbody (explicit style.width/height, dpr-scaled backing store, clipped to the tbody band) drawing per-hot-row edge haze: two 24px vertical-fade gradient strips off each hot row's top and bottom edges, ink = --accent mixed 20% into --muted at alpha 0.08. MOTION: displacement scale oscillates 1.5→3.0px on a per-row sine (period 2.2s, phase offset 0.4s per sorted row index — resorting re-phases the field); turbulence seed re-randomized every 4s for non-looping shimmer; SVG attribute writes throttled to 30fps inside one shared rAF that walks ONLY the hot-row list and fully clears/redraws the haze canvas each frame (no accumulation). Hover or focus-within on a hot row eases its displacement to 0 over 120ms (legibility snap), back over 400ms on leave, with a forced completion deadline at 500ms so fast hover-scrubs never strand rows mid-wobble. INTERACTION: header sort (sorting by heat re-phases shimmer), checkbox selection with token-relative fill (surface step + 1px foreground/muted mix ring via color-mix), hover raises row background one surface step (bg-foreground/[0.04]). TOKENS: haze ink derived from getComputedStyle(--accent, --muted) at mount, re-derived live via MutationObserver on documentElement class. SLEEP/TEARDOWN: rAF stops entirely when no row exceeds threshold or all hot rows are hover-frozen (scales pinned to 0, canvas cleared); IntersectionObserver pauses offscreen; ResizeObserver with zero-size tbody guard; all listeners/observers/rAF torn down on unmount. REDUCED MOTION: no filter, no canvas — hot rows get a static 2px --accent left rule and slightly elevated surface fill. DEMO: dashboard card 'Server load — last 24h' with mono timestamp, 10 believable ops rows (service, region, req/s, p95 ms, heat score), 3 hot at default threshold 70, footer row count + pagination stub, demo-level threshold segmented control.

Tags
tabledata-vizsvg-filterdisplacementheat-hazecanvassortabledashboard