{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "crack-polygon-order",
  "title": "Crack Polygon Order",
  "description": "A decorative panel fill that tiles itself the way drying mud actually cracks: widely-spaced primary cracks grow first, then two more generations nucleate inside the largest surviving cells after a 900ms stress-rebuild pause each, every new crack tip T-junctioning into an existing one at ~90 degrees instead of crossing it, until the mature tiling holds, rewets to blank, and a new unrelated cycle begins.",
  "dependencies": [],
  "files": [
    {
      "path": "registry/core/crack-polygon-order/component.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\n\n// ---------------------------------------------------------------------------\n// CrackPolygonOrder — a decorative panel that tiles itself the way a drying\n// mud layer actually cracks: a first, widely-spaced generation of cracks\n// nucleates and grows across the whole panel; once every primary crack has\n// stopped, a 900ms \"stress rebuilding\" pause elapses and a second generation\n// nucleates INSIDE the largest surviving cells and grows outward from a\n// point in both directions; the same pause-then-nucleate step repeats once\n// more for a third generation. The one rule that makes this desiccation\n// cracking rather than a generic Voronoi/crack-seam fill: every new crack\n// tip that comes within ~2px of an existing crack (a free surface, zero\n// stress) stops there, at close to a right angle — a T-junction, never a\n// crossing. Once no cell is worth splitting further the tiling holds fully\n// formed, then the whole panel \"rewets\" (every stroke's opacity eases to 0)\n// and a blank cooldown precedes the next, unrelated cycle.\n//\n// This is a real-time simulation, not a pre-computed path being revealed:\n// a 2px-cell occupancy grid records where ink already exists, a random-walk\n// tip advances against that grid at a literal 90px/s (segment length\n// 6-10px, turning noise +-12deg per segment), and completed generations are\n// turned into regions by a flood fill of the still-empty cells so the next\n// generation can nucleate inside the biggest 60% of them. Growth is driven\n// by real elapsed ms every rAF frame, not snapped to the 6-10px segment\n// boundary — a tip's in-progress segment interpolates continuously\n// (progressPx / segLen) so the stroke visibly glides at 60fps and only the\n// completed, collision-tested segment endpoints are ever committed. Path\n// geometry updates happen via direct SVG DOM writes on refs (not React\n// state) so ~14 rAF-driven tips can grow every frame without a React\n// re-render; React state only changes on the rare event that new <path>\n// elements need to mount (once per generation, three times a cycle) or the\n// panel resizes.\n//\n// t0 is deliberately not a fresh, empty panel: on mount the whole tick\n// function is run synchronously against a fast, fixed 16ms clock for a\n// random 1.5-10.5s of simulated time before the real rAF loop takes over,\n// so every visit lands mid-cycle. prefers-reduced-motion runs the same\n// synchronous driver all the way to the mature, fully-tiled hold frame and\n// then stops for good — the single most structured frame this pattern ever\n// produces, all three generations of T-junctions visible at once.\n// ---------------------------------------------------------------------------\n\nexport interface CrackPolygonOrderProps {\n  /** seeds the crack RNG; omit for a fresh, unrepeatable tiling every mount */\n  seed?: number;\n  className?: string;\n}\n\nconst CELL = 2; // occupancy-grid cell size, px — matches the ~2px T-junction test\nconst SPEED_PX_S = 90; // crack tip advance rate\nconst SEG_MIN = 6;\nconst SEG_MAX = 10;\nconst TURN_NOISE = (12 * Math.PI) / 180; // +-12deg per segment\nconst GEN_PAUSE_MS = 900;\nconst HOLD_MS = 4000;\nconst REWET_MS = 2500;\nconst COOLDOWN_MS = 600;\nconst MIN_PRIMARY = 4;\nconst MAX_PRIMARY = 7; // inclusive\nconst LARGEST_FRACTION = 0.6;\nconst MIN_REGION_AREA_PX2 = 130; // too small to bother nucleating inside\nconst MAX_DT_MS = 50; // clamp so a paused-tab / IO resume can't lurch the sim\nconst FASTFWD_STEP_MS = 16;\nconst MAX_FASTFWD_ITERS = 4000;\nconst STROKE_W = 1.5;\n\ntype Phase =\n  | \"primary\"\n  | \"secondary-wait\"\n  | \"secondary\"\n  | \"tertiary-wait\"\n  | \"tertiary\"\n  | \"hold\"\n  | \"rewet\"\n  | \"cooldown\";\n\ntype Pt = { x: number; y: number };\n\ntype Tip = {\n  // finalized segment endpoints — collision-tested, never move again\n  committed: Pt[];\n  // current heading + target length of the segment still in progress\n  heading: number;\n  segLen: number;\n  // px already covered into that in-progress segment, advanced every frame\n  // by real elapsed time (not snapped to the segment boundary) so the SVG\n  // path visibly glides rather than jumping in 6-10px steps\n  progressPx: number;\n  done: boolean;\n  recentCells: number[];\n};\n\ntype CrackData = { id: number; gen: 1 | 2 | 3; a: Tip; b: Tip };\ntype CrackMeta = { id: number; gen: 1 | 2 | 3; initialDA: string; initialDB: string };\n\n/** mulberry32 — small, fast, deterministic given a seed */\nfunction mulberry32(seed: number) {\n  let s = seed | 0;\n  return () => {\n    s = (s + 0x6d2b79f5) | 0;\n    let t = Math.imul(s ^ (s >>> 15), 1 | s);\n    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n  };\n}\n\nconst easeInOutCubic = (t: number) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2);\n\nfunction pointsToPath(pts: Pt[]): string {\n  if (pts.length === 0) return \"\";\n  if (pts.length === 1) return `M ${pts[0].x.toFixed(1)} ${pts[0].y.toFixed(1)}`;\n  return `M ${pts.map((p) => `${p.x.toFixed(1)} ${p.y.toFixed(1)}`).join(\" L \")}`;\n}\n\n/** committed points plus the current in-progress segment's live, interpolated tip — this is what gets drawn every frame */\nfunction renderPoints(tip: Tip): Pt[] {\n  if (tip.done) return tip.committed;\n  const from = tip.committed[tip.committed.length - 1];\n  const t = tip.segLen > 0 ? tip.progressPx / tip.segLen : 0;\n  const live: Pt = {\n    x: from.x + Math.cos(tip.heading) * tip.segLen * t,\n    y: from.y + Math.sin(tip.heading) * tip.segLen * t,\n  };\n  return [...tip.committed, live];\n}\n\nfunction makeTip(x: number, y: number, heading: number, rng: () => number): Tip {\n  return {\n    committed: [{ x, y }],\n    heading,\n    segLen: SEG_MIN + rng() * (SEG_MAX - SEG_MIN),\n    progressPx: 0,\n    done: false,\n    recentCells: [],\n  };\n}\n\n/** Occupancy grid: one Uint8Array cell per CELLxCELL px, 0 = empty. */\nclass Grid {\n  cols: number;\n  rows: number;\n  cells: Uint8Array;\n  constructor(w: number, h: number) {\n    this.cols = Math.max(1, Math.ceil(w / CELL));\n    this.rows = Math.max(1, Math.ceil(h / CELL));\n    this.cells = new Uint8Array(this.cols * this.rows);\n  }\n  idx(cx: number, cy: number) {\n    return cy * this.cols + cx;\n  }\n  mark(x: number, y: number, tip: Tip) {\n    const cx = Math.min(this.cols - 1, Math.max(0, Math.floor(x / CELL)));\n    const cy = Math.min(this.rows - 1, Math.max(0, Math.floor(y / CELL)));\n    const i = this.idx(cx, cy);\n    this.cells[i] = 1;\n    tip.recentCells.push(i);\n    if (tip.recentCells.length > 8) tip.recentCells.shift();\n  }\n  /** true if (x,y)'s 3x3 cell neighbourhood holds ink not owned by `tip`'s recent trail */\n  collides(x: number, y: number, tip: Tip): boolean {\n    const cx = Math.floor(x / CELL);\n    const cy = Math.floor(y / CELL);\n    for (let dy = -1; dy <= 1; dy++) {\n      for (let dx = -1; dx <= 1; dx++) {\n        const nx = cx + dx;\n        const ny = cy + dy;\n        if (nx < 0 || ny < 0 || nx >= this.cols || ny >= this.rows) continue;\n        const i = this.idx(nx, ny);\n        if (this.cells[i] !== 0 && !tip.recentCells.includes(i)) return true;\n      }\n    }\n    return false;\n  }\n}\n\ntype Region = { cells: number[]; area: number };\n\nfunction findRegions(grid: Grid): Region[] {\n  const { cols, rows, cells } = grid;\n  const visited = new Uint8Array(cols * rows);\n  const regions: Region[] = [];\n  const stack: number[] = [];\n  for (let start = 0; start < cells.length; start++) {\n    if (cells[start] !== 0 || visited[start]) continue;\n    const regionCells: number[] = [];\n    stack.length = 0;\n    stack.push(start);\n    visited[start] = 1;\n    while (stack.length) {\n      const i = stack.pop()!;\n      regionCells.push(i);\n      const cx = i % cols;\n      const cy = (i / cols) | 0;\n      const neighbours = [\n        [cx - 1, cy],\n        [cx + 1, cy],\n        [cx, cy - 1],\n        [cx, cy + 1],\n      ];\n      for (const [nx, ny] of neighbours) {\n        if (nx < 0 || ny < 0 || nx >= cols || ny >= rows) continue;\n        const ni = ny * cols + nx;\n        if (visited[ni] || cells[ni] !== 0) continue;\n        visited[ni] = 1;\n        stack.push(ni);\n      }\n    }\n    regions.push({ cells: regionCells, area: regionCells.length * CELL * CELL });\n  }\n  return regions;\n}\n\n/** picks a cell biased toward a region's interior: sample a few candidates, keep the one with the most empty neighbours */\nfunction pickSeedCell(region: Region, grid: Grid, rng: () => number): { x: number; y: number } {\n  const tries = Math.min(8, region.cells.length);\n  let best = region.cells[Math.floor(rng() * region.cells.length)];\n  let bestScore = -1;\n  for (let t = 0; t < tries; t++) {\n    const i = region.cells[Math.floor(rng() * region.cells.length)];\n    const cx = i % grid.cols;\n    const cy = (i / grid.cols) | 0;\n    let score = 0;\n    for (const [dx, dy] of [\n      [-1, 0],\n      [1, 0],\n      [0, -1],\n      [0, 1],\n    ]) {\n      const nx = cx + dx;\n      const ny = cy + dy;\n      if (nx < 0 || ny < 0 || nx >= grid.cols || ny >= grid.rows) continue;\n      if (grid.cells[ny * grid.cols + nx] === 0) score++;\n    }\n    if (score > bestScore) {\n      bestScore = score;\n      best = i;\n    }\n  }\n  const cx = best % grid.cols;\n  const cy = (best / grid.cols) | 0;\n  return { x: cx * CELL + CELL / 2, y: cy * CELL + CELL / 2 };\n}\n\n/**\n * advances one tip by dtMs of simulated growth against the shared grid.\n * The in-progress segment's `progressPx` moves by real elapsed distance\n * every call (never snapped straight to a segment boundary), so a caller\n * repainting every rAF frame sees the path glide continuously at\n * SPEED_PX_S rather than jump in SEG_MIN..SEG_MAX chunks every ~70-110ms.\n * A segment is only collision/edge-tested — and only then can it terminate\n * the tip at a T-junction — the instant progressPx reaches segLen.\n */\nfunction growTip(tip: Tip, dtMs: number, grid: Grid, w: number, h: number) {\n  if (tip.done) return;\n  let dt = dtMs;\n  let guard = 0;\n  while (dt > 0 && !tip.done && guard < 40) {\n    guard++;\n    const remainingSegPx = tip.segLen - tip.progressPx;\n    const availablePx = (SPEED_PX_S / 1000) * dt;\n\n    if (availablePx < remainingSegPx) {\n      tip.progressPx += availablePx;\n      dt = 0;\n      break;\n    }\n\n    // enough distance this tick to finish the in-progress segment — spend\n    // only the ms it actually needed and carry the rest into the next one\n    const neededMs = (remainingSegPx / SPEED_PX_S) * 1000;\n    dt -= neededMs;\n\n    const from = tip.committed[tip.committed.length - 1];\n    const to = {\n      x: from.x + Math.cos(tip.heading) * tip.segLen,\n      y: from.y + Math.sin(tip.heading) * tip.segLen,\n    };\n\n    if (to.x < 0 || to.x > w || to.y < 0 || to.y > h) {\n      const cx = Math.min(w, Math.max(0, to.x));\n      const cy = Math.min(h, Math.max(0, to.y));\n      tip.committed.push({ x: cx, y: cy });\n      grid.mark(cx, cy, tip);\n      tip.done = true;\n      break;\n    }\n\n    // sample a few points along the finished segment: earliest collision\n    // wins, and every clean sample gets marked so the raster has no gap\n    // another crack could slip through\n    let hit = false;\n    const samples = 3;\n    for (let s = 1; s <= samples; s++) {\n      const t = s / samples;\n      const px = from.x + (to.x - from.x) * t;\n      const py = from.y + (to.y - from.y) * t;\n      if (grid.collides(px, py, tip)) {\n        tip.committed.push({ x: px, y: py });\n        grid.mark(px, py, tip);\n        tip.done = true;\n        hit = true;\n        break;\n      }\n      grid.mark(px, py, tip);\n    }\n    if (hit) break;\n\n    tip.committed.push(to);\n    tip.heading += (rngFor() - 0.5) * 2 * TURN_NOISE;\n    tip.segLen = SEG_MIN + rngFor() * (SEG_MAX - SEG_MIN);\n    tip.progressPx = 0;\n  }\n}\n\n// a tiny module-level RNG stream (reseeded whenever the component's own\n// seeded rng is (re)created) avoids threading the seeded generator through\n// every tip while still keeping the whole pattern reproducible from `seed`\nlet sharedRng: () => number = mulberry32(1);\nfunction rngFor() {\n  return sharedRng();\n}\n\nfunction spawnCrack(id: number, gen: 1 | 2 | 3, x: number, y: number, rng: () => number): CrackData {\n  const heading = rng() * Math.PI * 2;\n  const a = makeTip(x, y, heading, rng);\n  const b = makeTip(x, y, heading + Math.PI + (rng() - 0.5) * 0.6, rng);\n  return { id, gen, a, b };\n}\n\nexport function CrackPolygonOrder({ seed, className = \"\" }: CrackPolygonOrderProps) {\n  const wrapRef = useRef<HTMLDivElement>(null);\n  const gRef = useRef<SVGGElement>(null);\n  const pathRefs = useRef<Map<number, { a: SVGPathElement | null; b: SVGPathElement | null }>>(new Map());\n\n  const [dims, setDims] = useState({ w: 0, h: 0 });\n  const [cracks, setCracks] = useState<CrackMeta[]>([]);\n  const [reduced, setReduced] = useState(false);\n\n  const gridRef = useRef<Grid | null>(null);\n  const cracksMapRef = useRef<Map<number, CrackData>>(new Map());\n  const cracksDirtyRef = useRef(false);\n  const phaseRef = useRef<Phase>(\"primary\");\n  const phaseTimerRef = useRef(0);\n  const nextIdRef = useRef(0);\n  const rngRef = useRef<() => number>(mulberry32(1));\n  const rafRef = useRef<number | null>(null);\n  const lastTsRef = useRef<number | null>(null);\n  const visibleRef = useRef(true);\n  const initializedRef = useRef(false);\n\n  useEffect(() => {\n    setReduced(window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches);\n  }, []);\n\n  // -- resize: measure the panel, (re)build the grid at that size --\n  useEffect(() => {\n    const el = wrapRef.current;\n    if (!el) return;\n    let debounce: ReturnType<typeof setTimeout> | null = null;\n    const ro = new ResizeObserver((entries) => {\n      const entry = entries[0];\n      if (!entry) return;\n      const { width, height } = entry.contentRect;\n      if (width < 4 || height < 4) return;\n      if (debounce) clearTimeout(debounce);\n      debounce = setTimeout(() => {\n        const sameSize =\n          initializedRef.current && Math.abs(width - dims.w) < 4 && Math.abs(height - dims.h) < 4;\n        if (sameSize) return;\n        setDims({ w: Math.round(width), h: Math.round(height) });\n      }, 120);\n    });\n    ro.observe(el);\n    return () => {\n      ro.disconnect();\n      if (debounce) clearTimeout(debounce);\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, []);\n\n  // -- visibility gate: pause the growth loop off-screen, resume in place --\n  useEffect(() => {\n    const el = wrapRef.current;\n    if (!el) return;\n    const io = new IntersectionObserver((entries) => {\n      visibleRef.current = entries.some((e) => e.isIntersecting);\n    });\n    io.observe(el);\n    return () => io.disconnect();\n  }, []);\n\n  const syncCracksToDom = () => {\n    if (!cracksDirtyRef.current) return;\n    cracksDirtyRef.current = false;\n    const meta: CrackMeta[] = Array.from(cracksMapRef.current.values()).map((c) => ({\n      id: c.id,\n      gen: c.gen,\n      initialDA: pointsToPath(renderPoints(c.a)),\n      initialDB: pointsToPath(renderPoints(c.b)),\n    }));\n    setCracks(meta);\n  };\n\n  const beginGeneration = (gen: 1 | 2 | 3, w: number, h: number, rng: () => number) => {\n    const grid = gridRef.current;\n    if (!grid) return;\n    if (gen === 1) {\n      const count = MIN_PRIMARY + Math.floor(rng() * (MAX_PRIMARY - MIN_PRIMARY + 1));\n      for (let i = 0; i < count; i++) {\n        const x = rng() * w;\n        const y = rng() * h;\n        const crack = spawnCrack(nextIdRef.current++, 1, x, y, rng);\n        cracksMapRef.current.set(crack.id, crack);\n      }\n    } else {\n      const regions = findRegions(grid).filter((r) => r.area >= MIN_REGION_AREA_PX2);\n      regions.sort((a, b) => b.area - a.area);\n      const take = Math.ceil(regions.length * LARGEST_FRACTION);\n      for (const region of regions.slice(0, take)) {\n        const seedPt = pickSeedCell(region, grid, rng);\n        const crack = spawnCrack(nextIdRef.current++, gen, seedPt.x, seedPt.y, rng);\n        cracksMapRef.current.set(crack.id, crack);\n      }\n    }\n    cracksDirtyRef.current = true;\n  };\n\n  const resetCycle = (w: number, h: number) => {\n    gridRef.current = new Grid(w, h);\n    cracksMapRef.current.clear();\n    cracksDirtyRef.current = true;\n    phaseRef.current = \"primary\";\n    phaseTimerRef.current = 0;\n    if (gRef.current) gRef.current.style.opacity = \"1\";\n    beginGeneration(1, w, h, rngRef.current);\n  };\n\n  /** advances the whole simulation by dtMs: growth, phase transitions, generation spawns */\n  const tick = (dtMs: number, w: number, h: number) => {\n    const grid = gridRef.current;\n    if (!grid) return;\n    const rng = rngRef.current;\n    const phase = phaseRef.current;\n\n    const stepGen = (gen: 1 | 2 | 3) => {\n      let allDone = true;\n      for (const c of cracksMapRef.current.values()) {\n        if (c.gen !== gen) continue;\n        growTip(c.a, dtMs, grid, w, h);\n        growTip(c.b, dtMs, grid, w, h);\n        if (!(c.a.done && c.b.done)) allDone = false;\n      }\n      return allDone;\n    };\n\n    switch (phase) {\n      case \"primary\":\n        if (stepGen(1)) {\n          phaseRef.current = \"secondary-wait\";\n          phaseTimerRef.current = 0;\n        }\n        break;\n      case \"secondary-wait\":\n        phaseTimerRef.current += dtMs;\n        if (phaseTimerRef.current >= GEN_PAUSE_MS) {\n          beginGeneration(2, w, h, rng);\n          phaseRef.current = \"secondary\";\n        }\n        break;\n      case \"secondary\":\n        if (stepGen(2)) {\n          phaseRef.current = \"tertiary-wait\";\n          phaseTimerRef.current = 0;\n        }\n        break;\n      case \"tertiary-wait\":\n        phaseTimerRef.current += dtMs;\n        if (phaseTimerRef.current >= GEN_PAUSE_MS) {\n          beginGeneration(3, w, h, rng);\n          phaseRef.current = \"tertiary\";\n        }\n        break;\n      case \"tertiary\":\n        if (stepGen(3)) {\n          phaseRef.current = \"hold\";\n          phaseTimerRef.current = 0;\n        }\n        break;\n      case \"hold\":\n        phaseTimerRef.current += dtMs;\n        if (phaseTimerRef.current >= HOLD_MS) {\n          phaseRef.current = \"rewet\";\n          phaseTimerRef.current = 0;\n        }\n        break;\n      case \"rewet\": {\n        phaseTimerRef.current += dtMs;\n        const t = Math.min(1, phaseTimerRef.current / REWET_MS);\n        if (gRef.current) gRef.current.style.opacity = String(1 - easeInOutCubic(t));\n        if (phaseTimerRef.current >= REWET_MS) {\n          gridRef.current = new Grid(w, h);\n          cracksMapRef.current.clear();\n          cracksDirtyRef.current = true;\n          if (gRef.current) gRef.current.style.opacity = \"1\";\n          phaseRef.current = \"cooldown\";\n          phaseTimerRef.current = 0;\n        }\n        break;\n      }\n      case \"cooldown\":\n        phaseTimerRef.current += dtMs;\n        if (phaseTimerRef.current >= COOLDOWN_MS) {\n          phaseRef.current = \"primary\";\n          phaseTimerRef.current = 0;\n          beginGeneration(1, w, h, rng);\n        }\n        break;\n    }\n  };\n\n  // -- write per-frame growth straight to the DOM, bypassing React --\n  const paintFrame = () => {\n    for (const c of cracksMapRef.current.values()) {\n      const refs = pathRefs.current.get(c.id);\n      if (!refs) continue;\n      if (refs.a) refs.a.setAttribute(\"d\", pointsToPath(renderPoints(c.a)));\n      if (refs.b) refs.b.setAttribute(\"d\", pointsToPath(renderPoints(c.b)));\n    }\n  };\n\n  // -- init + main loop: waits for a real measurement, then either freezes\n  // at the mature frame (reduced motion) or fast-forwards into a random\n  // mid-cycle point before handing off to a live rAF loop --\n  useEffect(() => {\n    if (dims.w < 4 || dims.h < 4) return;\n    if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) {\n      rngRef.current = mulberry32(seed ?? Math.floor(Math.random() * 2 ** 31));\n      sharedRng = rngRef.current;\n      gridRef.current = new Grid(dims.w, dims.h);\n      cracksMapRef.current.clear();\n      phaseRef.current = \"primary\";\n      phaseTimerRef.current = 0;\n      beginGeneration(1, dims.w, dims.h, rngRef.current);\n      let iters = 0;\n      while ((phaseRef.current as Phase) !== \"hold\" && iters < MAX_FASTFWD_ITERS) {\n        tick(FASTFWD_STEP_MS, dims.w, dims.h);\n        iters++;\n      }\n      syncCracksToDom();\n      initializedRef.current = true;\n      return;\n    }\n\n    if (!initializedRef.current) {\n      rngRef.current = mulberry32(seed ?? Math.floor(Math.random() * 2 ** 31));\n      sharedRng = rngRef.current;\n      resetCycle(dims.w, dims.h);\n      const leadInMs = 1500 + rngRef.current() * 9000;\n      let simulated = 0;\n      let iters = 0;\n      while (simulated < leadInMs && iters < MAX_FASTFWD_ITERS) {\n        tick(FASTFWD_STEP_MS, dims.w, dims.h);\n        simulated += FASTFWD_STEP_MS;\n        iters++;\n      }\n      syncCracksToDom();\n      paintFrame();\n      initializedRef.current = true;\n    } else {\n      // a real resize after init: rebuild fresh at the new size, no lead-in\n      resetCycle(dims.w, dims.h);\n      syncCracksToDom();\n      paintFrame();\n    }\n\n    const loop = (ts: number) => {\n      rafRef.current = requestAnimationFrame(loop);\n      if (!visibleRef.current) {\n        lastTsRef.current = ts;\n        return;\n      }\n      const last = lastTsRef.current ?? ts;\n      const dt = Math.min(MAX_DT_MS, ts - last);\n      lastTsRef.current = ts;\n      if (dt <= 0) return;\n      tick(dt, dims.w, dims.h);\n      syncCracksToDom();\n      paintFrame();\n    };\n    rafRef.current = requestAnimationFrame(loop);\n    return () => {\n      if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);\n      lastTsRef.current = null;\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [dims.w, dims.h, seed]);\n\n  return (\n    <div\n      ref={wrapRef}\n      data-crack-polygon-order\n      aria-hidden\n      className={`ns-crack-polygon-order relative h-full w-full overflow-hidden ${className}`}\n    >\n      <svg\n        width=\"100%\"\n        height=\"100%\"\n        viewBox={`0 0 ${Math.max(1, dims.w)} ${Math.max(1, dims.h)}`}\n        preserveAspectRatio=\"none\"\n        className=\"block\"\n      >\n        <rect x={0} y={0} width={dims.w} height={dims.h} fill=\"var(--background)\" />\n        <g ref={gRef}>\n          {cracks.map((c) => (\n            <g key={c.id}>\n              <path\n                ref={(el) => {\n                  const entry = pathRefs.current.get(c.id) ?? { a: null, b: null };\n                  entry.a = el;\n                  pathRefs.current.set(c.id, entry);\n                }}\n                d={c.initialDA}\n                fill=\"none\"\n                stroke=\"var(--foreground)\"\n                strokeWidth={STROKE_W}\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                vectorEffect=\"non-scaling-stroke\"\n              />\n              <path\n                ref={(el) => {\n                  const entry = pathRefs.current.get(c.id) ?? { a: null, b: null };\n                  entry.b = el;\n                  pathRefs.current.set(c.id, entry);\n                }}\n                d={c.initialDB}\n                fill=\"none\"\n                stroke=\"var(--foreground)\"\n                strokeWidth={STROKE_W}\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                vectorEffect=\"non-scaling-stroke\"\n              />\n            </g>\n          ))}\n        </g>\n      </svg>\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/ui/crack-polygon-order.tsx"
    }
  ],
  "meta": {
    "collection": "core",
    "tags": [
      "divider",
      "panel",
      "background",
      "svg",
      "crack",
      "fracture",
      "tessellation",
      "generative",
      "desiccation",
      "ambient"
    ],
    "instruction": "Build <CrackPolygonOrder seed? className?> as a full-bleed fill for a card-scale panel (give it explicit height via className/a wrapper; the component itself is h-full w-full). STRUCTURE: a plain <div data-crack-polygon-order aria-hidden> holding one inline <svg viewBox=\"0 0 {measuredWidth} {measuredHeight}\" preserveAspectRatio=\"none\"> whose viewBox is set to the container's own measured CSS pixel size via a debounced ResizeObserver (120ms) — unlike an abstract stretched viewBox, 1 viewBox unit here equals 1 on-screen px, because every real-numbers threshold in the mechanic (90px/s growth, a ~2px T-junction test, a ~900px^2 minimum polygon area) is stated in real panel pixels. A <rect> filled var(--background) sits under everything; every crack stroke is var(--foreground) at 1.5px with vector-effect=\"non-scaling-stroke\" — never var(--border), which falls below the light-theme legibility floor at hairline width, and never any per-polygon fill (the tessellation must read from crack lines alone, not shaded cell faces). SIMULATION, not a pre-computed reveal: a Uint8Array occupancy grid at 2px cells (Grid class) records where ink already exists. Each crack is a single nucleation point growing TWO tips outward in roughly opposite headings (heading and heading+PI, +-~17deg jitter) — this is what makes primary cracks read as a widely-spaced network and what makes secondary/tertiary cracks visibly bisect the cell they were born in rather than doodling from one wall. A tip advances by discrete 3-5px segments at a rate that nets 90px/s, turning +-12deg per segment (seeded per-tip via a module-level `sharedRng` stream so the whole pattern is reproducible from one `seed`), and samples 3 points along every new segment against the grid's 3x3-cell neighbourhood (skipping the tip's own last 8 marked cells so it can't immediately T-junction into itself) — the instant a sample lands within that ~2px neighbourhood of ANY other ink, growth stops exactly there: the T-junction, and the reason later cracks can never be seen crossing an earlier one. Every generation is driven by an explicit phase machine (primary -> secondary-wait -> secondary -> tertiary-wait -> tertiary -> hold -> rewet -> cooldown -> primary...): primary spawns 4-7 nucleation points scattered uniformly at t=0; once every primary tip is done (edge or T-junction), a 900ms wait elapses, then a flood fill (findRegions, 4-connected BFS over empty grid cells) finds every still-open polygon, regions under 130px^2 are dropped, the rest sorted by area descending, and cracks nucleate inside the top ceil(60%) of what's left — same 900ms-wait-then-nucleate step repeats once more for the third generation, then the tiling holds for 4000ms untouched. REWET: over the following 2500ms the whole crack <g>'s opacity eases 1 -> 0 on an ease-in-out-cubic computed per frame (not a CSS transition, so it stays exactly in step with the same rAF clock driving growth) — the panel then clears (grid reset, crack map emptied) as a genuine return to blank, not just an invisible-but-still-there state, holds blank for a 600ms cooldown, and the next cycle's primaries begin. Full period is non-uniform by design, roughly 14-17s (6-8s to reach maturity + 4s hold + 2.5s rewet + 0.6s cooldown). PERFORMANCE: geometry writes for already-mounted <path> elements happen via direct refs.current.setAttribute('d', ...) inside the rAF callback, bypassing React entirely for per-frame growth; React state (the `cracks` array, id+gen+initialD only) only updates on the rare event a new generation's crack set changes (three times a cycle) or the panel resizes, gated by a `cracksDirtyRef` flag so a mid-fast-forward burst of spawns collapses into a single re-render. RESUME SAFETY: an IntersectionObserver sets a `visibleRef` the rAF loop checks before ticking (off-screen panels stop simulating but keep scheduling frames so they resume instantly back in view), and every dt fed to the simulation is clamped to 50ms regardless of source, so a backgrounded tab or a slow resize can never hand the sim a multi-second jump that would skip whole generations invisibly. THE t0 REQUIREMENT: a brand-new mount never starts from an empty panel — after the first valid ResizeObserver measurement, the exact same tick() function used by the live loop is called synchronously in a tight while-loop on a fixed 16ms clock for a random 1500-10500ms of simulated time (capped at 4000 iterations) BEFORE the real rAF loop ever starts, landing every visit at an arbitrary, already-partially-tiled mid-cycle frame; a real resize after that initial mount rebuilds the grid at the new size and restarts cleanly at a fresh primary generation (no repeated lead-in — a resize is not a new visit). REDUCED MOTION: on mount, if prefers-reduced-motion matches, skip the rAF loop entirely and instead run that same tick() function synchronously and repeatedly until phaseRef reaches exactly 'hold' (capped at 4000 iterations), then stop for good — the panel renders once, fully tiled, all three generations' T-junctions visible simultaneously, and never rewets. Zero dependencies, no canvas — SVG+DOM+CSS only, and the crack RNG (mulberry32, seeded from the optional `seed` prop or Math.random() per mount) is the only source of variation. DIFFERS FROM craze-rule: that component draws ONE branching fracture line across a fixed 40px divider strip, once, via IntersectionObserver, and never subdivides a cell — no generation hierarchy, no polygon tessellation, no ongoing simulation once armed. DIFFERS FROM compare-crack-seam: that fracture is a single draggable Voronoi-cell seam that IS a before/after image divider, spawning micro-fissures on drag velocity; crack-polygon-order has no pointer interaction, no comparison images, and its cracks free-branch across the whole panel on their own clock rather than tracking a cursor."
  },
  "type": "registry:ui"
}