{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "sula-field",
  "type": "registry:ui",
  "dependencies": [
    "clsx",
    "motion",
    "ogl",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "https://usva.build/r/sula-motion.json",
    "https://usva.build/r/sula-core.json"
  ],
  "files": [
    {
      "path": "field-geometry.ts",
      "target": "components/ui/field-geometry.ts",
      "type": "registry:ui",
      "content": "import { type Blob, bridgeNecks, type Neck } from \"./geometry\";\nimport { clamp01, smoother } from \"./curves\";\n\nexport interface DriftOpts {\n  width: number;\n  height: number;\n  /** Feeds every path phase, so a seed reproduces the same wander. */\n  seed: number;\n}\n\nexport interface FieldGeometry {\n  /** Two huge, dark, shoulder-off-canvas masses: the atmosphere, no rim. */\n  back: Blob[];\n  /** Lit actors and glints that drift, kiss and part in front. */\n  front: Blob[];\n  /** Scheduled-kiss bridges among the front actors. */\n  necks: Neck[];\n}\n\nconst TAU = Math.PI * 2;\n\n/** The full loop closes at 96 s: every band's cycle count is an integer, so the\n * whole field returns to its start seamlessly and deterministically. */\nexport const LOOP_T = 96;\n\n/** Fraction of the short side used as the front merge radius. Higher than the\n * old single-pass k so a kiss reads as a fat waist, not a thread. */\nexport const FRONT_K_FRACTION = 0.11;\n/** The back pass merges soupy and huge, so its two anchors read as one cloud. */\nexport const BACK_K_FRACTION = 0.2;\n\n/** Eased approach of the pointer lean, per frame. */\nexport const POINTER_EASE = 0.08;\n/** The heavy wake that drags a blob toward the cursor is slower still: a liquid\n * has mass, so it lags. */\nexport const WAKE_EASE = 0.03;\n\n/**\n * A deterministic hash in [0, 1) from an integer stream and the seed. Only has to\n * look unpatterned and stay identical across renders so the drift is stable for\n * SSR and tests.\n */\nfunction hash(n: number, seed: number): number {\n  const x = Math.sin((n + 1) * 12.9898 + seed * 78.233) * 43758.5453;\n  return x - Math.floor(x);\n}\n\nconst clamp = (v: number, lo: number, hi: number): number =>\n  Math.min(hi, Math.max(lo, v));\n\n/** Three incommensurate sine bands with descending weight. Integer cycle counts\n * over LOOP_T keep it seamless; the coprime-ish triples keep the period from\n * ever visibly repeating inside two minutes. */\nconst BAND_WEIGHTS = [0.6, 0.3, 0.1];\nfunction drift(\n  time: number,\n  amp: number,\n  cycles: readonly [number, number, number],\n  phase: number,\n): number {\n  let s = 0;\n  for (let j = 0; j < 3; j++) {\n    const f = ((cycles[j] as number) * TAU) / LOOP_T;\n    s += (BAND_WEIGHTS[j] as number) * Math.sin(time * f + phase + j * 2.399);\n  }\n  return amp * s;\n}\n\ninterface Spec {\n  baseX: number;\n  baseY: number;\n  r: number;\n  amp: number;\n  cyclesX: readonly [number, number, number];\n  cyclesY: readonly [number, number, number];\n  breath: number;\n  offCanvas: boolean;\n}\n\nfunction place(spec: Spec, time: number, opts: DriftOpts, id: number): Blob {\n  const { width, height, seed } = opts;\n  const px = hash(id * 16 + 6, seed) * TAU;\n  const py = hash(id * 16 + 7, seed) * TAU;\n  const breathe =\n    1 +\n    spec.breath *\n      Math.sin((time * TAU) / LOOP_T + hash(id * 16 + 8, seed) * TAU);\n  const r = spec.r * breathe;\n  const dx = drift(time, spec.amp, spec.cyclesX, px);\n  const dy = drift(time, spec.amp, spec.cyclesY, py);\n  const slackX = spec.offCanvas ? 0.35 * r : r;\n  const slackY = spec.offCanvas ? 0.35 * r : r;\n  const cx = clamp(spec.baseX * width + dx, -slackX, width + slackX);\n  const cy = clamp(spec.baseY * height + dy, -slackY, height + slackY);\n  return { cx, cy, hw: r, hh: r, r };\n}\n\n/** One scheduled encounter: which two front actors meet, when in the loop, and\n * how wide the window is (as loop fractions). */\nconst KISSES: ReadonlyArray<{\n  i: number;\n  j: number;\n  at: number;\n  half: number;\n}> = [\n  { i: 0, j: 1, at: 0.22, half: 0.06 },\n  { i: 1, j: 2, at: 0.61, half: 0.06 },\n  { i: 0, j: 2, at: 0.87, half: 0.05 },\n];\n\nexport function fieldFrame(time: number, opts: DriftOpts): FieldGeometry {\n  const { width, height } = opts;\n  const short = Math.min(width, height);\n\n  const backSpecs: Spec[] = [\n    {\n      baseX: 0.16,\n      baseY: 0.84,\n      r: short * 0.45,\n      amp: short * 0.04,\n      cyclesX: [1, 3, 7],\n      cyclesY: [2, 5, 11],\n      breath: 0.06,\n      offCanvas: true,\n    },\n    {\n      baseX: 0.86,\n      baseY: 0.2,\n      r: short * 0.34,\n      amp: short * 0.045,\n      cyclesX: [3, 7, 13],\n      cyclesY: [1, 5, 9],\n      breath: 0.06,\n      offCanvas: true,\n    },\n  ];\n\n  const midSpecs: Spec[] = [\n    { baseX: 0.3, baseY: 0.68, r: short * 0.16 },\n    { baseX: 0.52, baseY: 0.5, r: short * 0.13 },\n    { baseX: 0.68, baseY: 0.36, r: short * 0.11 },\n  ].map((s) => ({\n    ...s,\n    amp: short * 0.12,\n    cyclesX: [2, 5, 11],\n    cyclesY: [3, 8, 13],\n    breath: 0.05,\n    offCanvas: false,\n  }));\n\n  const glintSpecs: Spec[] = [\n    { baseX: 0.72, baseY: 0.72, r: short * 0.05 },\n    { baseX: 0.34, baseY: 0.24, r: short * 0.035 },\n  ].map((s) => ({\n    ...s,\n    amp: short * 0.18,\n    cyclesX: [3, 8, 13],\n    cyclesY: [5, 13, 21],\n    breath: 0.04,\n    offCanvas: false,\n  }));\n\n  const back = backSpecs.map((s, i) => place(s, time, opts, i));\n  const mid = midSpecs.map((s, i) => place(s, time, opts, 10 + i));\n  const glints = glintSpecs.map((s, i) => place(s, time, opts, 20 + i));\n\n  const k = short * FRONT_K_FRACTION;\n  const loopPhase = (((time % LOOP_T) + LOOP_T) % LOOP_T) / LOOP_T;\n  const necks: Neck[] = [];\n  for (const kiss of KISSES) {\n    const w = 1 - clamp01(Math.abs(loopPhase - kiss.at) / kiss.half);\n    if (w <= 0) continue;\n    const ramp = smoother(w);\n    const a = mid[kiss.i] as Blob;\n    const b = mid[kiss.j] as Blob;\n    const dx = b.cx - a.cx;\n    const dy = b.cy - a.cy;\n    const dist = Math.hypot(dx, dy) || 1;\n    const ux = dx / dist;\n    const uy = dy / dist;\n    const midX = (a.cx + b.cx) / 2;\n    const midY = (a.cy + b.cy) / 2;\n    const pull = ramp * 0.92;\n    a.cx += (midX - ux * (a.r + 0.15 * k) - a.cx) * pull;\n    a.cy += (midY - uy * (a.r + 0.15 * k) - a.cy) * pull;\n    b.cx += (midX + ux * (b.r + 0.15 * k) - b.cx) * pull;\n    b.cy += (midY + uy * (b.r + 0.15 * k) - b.cy) * pull;\n    const stretch = 1 + 0.1 * ramp;\n    a.hw *= stretch;\n    b.hw *= stretch;\n    necks.push(...bridgeNecks([a, b], k, ramp));\n  }\n\n  return { back, front: [...mid, ...glints], necks };\n}\n\n/** Finds the surface under pressure, rather than making the whole veil follow\n * the pointer through its first (and largest) blob. */\nexport function nearestBlob(\n  blobs: Blob[],\n  point: { x: number; y: number },\n): Blob | undefined {\n  let nearest: Blob | undefined;\n  let nearestDistance = Number.POSITIVE_INFINITY;\n  for (const blob of blobs) {\n    const distance =\n      Math.hypot(blob.cx - point.x, blob.cy - point.y) -\n      Math.max(blob.hw, blob.hh);\n    if (distance < nearestDistance) {\n      nearest = blob;\n      nearestDistance = distance;\n    }\n  }\n  return nearest;\n}\n"
    },
    {
      "path": "drive.ts",
      "target": "components/ui/drive.ts",
      "type": "registry:ui",
      "content": "import type { Blob, Neck } from \"./geometry\";\nimport {\n  BACK_K_FRACTION,\n  FRONT_K_FRACTION,\n  fieldFrame,\n} from \"./field-geometry\";\n\n/**\n * How many bodies each depth plane may hold, and how many necks the whole frame\n * may hold. The shader's uniform arrays are fixed, so these are hard ceilings:\n * a drive that hands back more is clamped to the first N, and told so once in\n * development. Silently dropping the tail is how a tear ends up with no neck.\n */\nexport const MAX_FIELD_BLOBS = 12;\nexport const MAX_FIELD_NECKS = 8;\n\n/** A rounded box of fluid, in CSS pixels from the field's top-left corner. */\nexport interface SulaBlob {\n  cx: number;\n  cy: number;\n  /** Corner radius. A body with hw = hh = r is a circle. */\n  r: number;\n  /** Half-width. Defaults to `r`. */\n  hw?: number;\n  /** Half-height. Defaults to `r`. */\n  hh?: number;\n}\n\n/**\n * A capsule joining two points: the tether of a tear, or the waist of a merge.\n * `strength` fades the bridge back into the surface without thinning it, which\n * is the only way a neck melts instead of snapping. Defaults to a solid bridge.\n */\nexport interface SulaNeck {\n  ax: number;\n  ay: number;\n  bx: number;\n  by: number;\n  r: number;\n  strength?: number;\n}\n\n/** The field's box, in CSS pixels, plus the seed the consumer was handed. */\nexport interface SulaFieldBounds {\n  width: number;\n  height: number;\n  seed: number;\n}\n\n/**\n * What the fluid is doing at one instant. The consumer says what the material is\n * doing; the field decides how to paint it.\n */\nexport interface SulaDriveFrame {\n  /** Matte bodies behind everything, drawn without a rim. Depth, not actors. */\n  back?: SulaBlob[];\n  /** The lit bodies. */\n  front?: SulaBlob[];\n  /** Bridges among the front bodies. */\n  necks?: SulaNeck[];\n  /** Merge radius of the front plane, in px. Defaults to 11% of the short side. */\n  mergeRadius?: number;\n  /** Merge radius of the back plane, in px. Defaults to 20% of the short side. */\n  backMergeRadius?: number;\n}\n\n/**\n * A pure function of time. Given the seconds elapsed (already scaled by `speed`)\n * and the field's bounds, it returns the frame. Deterministic by contract: the\n * same time and bounds must give the same frame, so a drive can be unit-tested\n * and a still frame under reduced motion is just the frame at t = 0.\n */\nexport type SulaFieldDrive = (\n  time: number,\n  bounds: SulaFieldBounds,\n) => SulaDriveFrame;\n\nexport interface ResolvedDriveFrame {\n  back: Blob[];\n  front: Blob[];\n  necks: Neck[];\n  kFront: number;\n  kBack: number;\n  /** True when the drive handed back more than a plane can hold. */\n  clamped: boolean;\n}\n\nconst toBlob = (b: SulaBlob): Blob => ({\n  cx: b.cx,\n  cy: b.cy,\n  hw: b.hw ?? b.r,\n  hh: b.hh ?? b.r,\n  r: b.r,\n});\n\n/**\n * Turns whatever the drive said into exactly what the renderer can take: plane\n * budgets enforced, half-extents filled in, merge radii defaulted off the short\n * side so a drive never has to know the field's pixel size to look right.\n */\nexport function resolveDriveFrame(\n  frame: SulaDriveFrame,\n  bounds: SulaFieldBounds,\n): ResolvedDriveFrame {\n  const short = Math.min(bounds.width, bounds.height);\n  const back = frame.back ?? [];\n  const front = frame.front ?? [];\n  const necks = frame.necks ?? [];\n  return {\n    back: back.slice(0, MAX_FIELD_BLOBS).map(toBlob),\n    front: front.slice(0, MAX_FIELD_BLOBS).map(toBlob),\n    necks: necks.slice(0, MAX_FIELD_NECKS).map((n) => ({ ...n })),\n    kFront: frame.mergeRadius ?? short * FRONT_K_FRACTION,\n    kBack: frame.backMergeRadius ?? short * BACK_K_FRACTION,\n    clamped:\n      back.length > MAX_FIELD_BLOBS ||\n      front.length > MAX_FIELD_BLOBS ||\n      necks.length > MAX_FIELD_NECKS,\n  };\n}\n\n/** The built-in choreography: the ambient drift the field has always had. */\nexport const ambientDrift: SulaFieldDrive = (time, bounds) => {\n  const { back, front, necks } = fieldFrame(time, bounds);\n  return { back, front, necks };\n};\n"
    },
    {
      "path": "sula-field.tsx",
      "target": "components/ui/sula-field.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\nimport { useReducedMotion } from \"motion/react\";\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  createField,\n  type FieldColors,\n  liftTint,\n  resolveColor,\n  shineForBackdrop,\n} from \"./field\";\nimport { packHover, packUniforms } from \"./geometry\";\nimport { createPauseGate } from \"./pause\";\nimport { useContextRecovery } from \"./recovery\";\nimport {\n  ambientDrift,\n  MAX_FIELD_BLOBS,\n  MAX_FIELD_NECKS,\n  resolveDriveFrame,\n  type SulaFieldDrive,\n} from \"./drive\";\nimport { nearestBlob, POINTER_EASE, WAKE_EASE } from \"./field-geometry\";\n\n// Bundlers replace this expression statically, but a Vite app types itself with\n// `types: [\"vite/client\"]` and has no `process`, so copied source fails tsc\n// without a local declaration. Module scope, so it shadows nothing in Next.\ndeclare const process: { env: { NODE_ENV?: string } };\n\nexport interface SulaFieldProps extends React.HTMLAttributes<HTMLDivElement> {\n  /** Drift-rate multiplier; higher drifts faster. Defaults to 1. */\n  speed?: number;\n  /**\n   * The choreography: a pure function of time and the field's bounds, returning\n   * the bodies and necks for that instant. Defaults to the ambient drift. Each\n   * depth plane holds up to MAX_FIELD_BLOBS bodies and the frame up to\n   * MAX_FIELD_NECKS necks; anything past that is clamped away, so keep inside it.\n   */\n  drive?: SulaFieldDrive;\n  /** When on, blobs lean toward the eased cursor. Defaults to false. */\n  interactive?: boolean;\n  /** Reproduces the same wander for a given value. Defaults to 0. */\n  seed?: number;\n  /** false mounts no canvas; reduced-motion paints one static frame. */\n  fluid?: boolean;\n  /**\n   * Which instant of the drive the reduced-motion still frame is taken from, in\n   * seconds. Defaults to 0, which is right for a drive that is already composed\n   * at rest, and wrong for one that has to run before there is anything to see:\n   * a cycle that lifts bodies out of a pool is an empty pool at t=0. Pick the\n   * moment that reads as the whole idea, held.\n   */\n  stillTime?: number;\n  accentColor?: string;\n  backdrop?: string;\n  tint?: string;\n  shine?: number;\n  children?: React.ReactNode;\n}\n\nconst MAX_DPR = 2;\n/** Peak surface undulation, per depth pass. The back cloud heaves slowly; the\n * front actors shimmer tighter. */\nconst BACK_WOBBLE = 2.5;\nconst FRONT_WOBBLE = 1.2;\n/** Peak edge displacement of the pointer lean, in px. */\nconst HOVER_WOBBLE = 1.4;\n/** Peak position lean toward the cursor, as a fraction of the short side: the\n * heavy wake that trails the pointer. */\nconst WAKE_REACH = 0.06;\n/** Gaussian falloff radius of the wake, as a fraction of the short side. Every\n * actor within it leans, weighted by proximity, so the pull glides across the\n * field instead of snapping from one nearest blob to the next. */\nconst WAKE_SPREAD = 0.45;\n\nfunction readColors(\n  node: HTMLElement,\n  overrides: {\n    backdrop?: string;\n    tint?: string;\n    accent?: string;\n    shine?: number;\n  },\n): FieldColors {\n  const styles = getComputedStyle(node);\n  const token = (name: string) => styles.getPropertyValue(name).trim();\n  const backdrop = resolveColor(overrides.backdrop ?? token(\"--usva-bg\"));\n  const tintToken =\n    overrides.tint ?? token(\"--usva-surface-2\") ?? token(\"--usva-surface\");\n  return {\n    backdrop,\n    tint: resolveColor(tintToken || token(\"--usva-surface\")),\n    accent: resolveColor(overrides.accent ?? token(\"--usva-accent\")),\n    shine: overrides.shine ?? shineForBackdrop(backdrop),\n  };\n}\n\nexport const SulaField = React.forwardRef<HTMLDivElement, SulaFieldProps>(\n  (\n    {\n      speed = 1,\n      drive,\n      interactive = false,\n      seed = 0,\n      fluid = true,\n      stillTime = 0,\n      accentColor,\n      backdrop,\n      tint,\n      shine,\n      className,\n      children,\n      ...props\n    },\n    forwardedRef,\n  ) => {\n    const reduced = useReducedMotion();\n    const containerRef = React.useRef<HTMLDivElement | null>(null);\n    const canvasRef = React.useRef<HTMLCanvasElement | null>(null);\n\n    /* These tune what the loop draws, not the GL field, so they ride refs rather\n     * than effect deps: a live prop change must never tear the context down and\n     * rebuild it on the same canvas, which races a scheduled frame. */\n    const speedRef = React.useRef(speed);\n    speedRef.current = speed;\n    const seedRef = React.useRef(seed);\n    seedRef.current = seed;\n    const interactiveRef = React.useRef(interactive);\n    interactiveRef.current = interactive;\n    const driveRef = React.useRef<SulaFieldDrive>(drive ?? ambientDrift);\n    driveRef.current = drive ?? ambientDrift;\n\n    const { failed, generation, onContextLost, onContextReady } =\n      useContextRecovery(canvasRef);\n    const overrides = React.useMemo(\n      () => ({ backdrop, tint, accent: accentColor, shine }),\n      [backdrop, tint, accentColor, shine],\n    );\n    const overridesRef = React.useRef(overrides);\n    overridesRef.current = overrides;\n    /* This surface swaps two colour sets per pass, so a retune recomputes both\n     * rather than pushing one through setColors, which the next pass would\n     * overwrite anyway. */\n    const refreshRef = React.useRef<(() => void) | null>(null);\n    const [mounted, setMounted] = React.useState(false);\n    React.useEffect(() => setMounted(true), []);\n\n    const animated = fluid && !reduced && !failed && mounted;\n    const still = fluid && reduced && !failed && mounted;\n    const fieldOn = animated || still;\n\n    // biome-ignore lint/correctness/useExhaustiveDependencies: `generation` is not read here, it is what rebuilds the field on a restored context\n    React.useEffect(() => {\n      if (!fieldOn) return;\n      const canvas = canvasRef.current;\n      const container = containerRef.current;\n      if (!canvas || !container) return;\n\n      let base = readColors(container, overridesRef.current);\n      let backColors: FieldColors = { ...base, shine: 0 };\n      let frontColors: FieldColors = {\n        ...base,\n        tint: liftTint(base.tint, base.accent),\n      };\n      const refreshColors = () => {\n        base = readColors(container, overridesRef.current);\n        backColors = { ...base, shine: 0 };\n        frontColors = { ...base, tint: liftTint(base.tint, base.accent) };\n      };\n      refreshRef.current = refreshColors;\n\n      const field = createField({\n        canvas,\n        colors: frontColors,\n        onContextLost,\n      });\n      if (!field) {\n        onContextLost();\n        return;\n      }\n      onContextReady();\n\n      let width = 0;\n      let height = 0;\n      let dpr = 1;\n      const measure = () => {\n        const box = container.getBoundingClientRect();\n        const w = Math.ceil(box.width);\n        const h = Math.ceil(box.height);\n        if (w <= 0 || h <= 0) return false;\n        const nextDpr = Math.min(window.devicePixelRatio || 1, MAX_DPR);\n        if (w !== width || h !== height || nextDpr !== dpr) {\n          canvas.style.width = `${w}px`;\n          canvas.style.height = `${h}px`;\n          field.resize(w, h, nextDpr);\n        }\n        width = w;\n        height = h;\n        dpr = nextDpr;\n        return true;\n      };\n\n      let hoverAmt = 0;\n      let hoverTarget = 0;\n      let wakeAmt = 0;\n      const pointer = { x: 0, y: 0 };\n\n      /* Two passes into one context: a dark, soupy back cloud clears the frame,\n       * then the lit actors composite over it (clear:false). Colors are set per\n       * pass, so the back stays matte (shine 0) and the front carries the rim. */\n      let warned = false;\n      const draw = (elapsed: number) => {\n        const bounds = { width, height, seed: seedRef.current };\n        const resolved = resolveDriveFrame(\n          driveRef.current(elapsed * speedRef.current, bounds),\n          bounds,\n        );\n        const { back, front, necks, kFront, kBack } = resolved;\n        if (resolved.clamped && !warned) {\n          warned = true;\n          if (process.env.NODE_ENV !== \"production\") {\n            console.warn(\n              `SulaField: the drive returned more than ${MAX_FIELD_BLOBS} bodies in a plane or ${MAX_FIELD_NECKS} necks. The surplus is not drawn.`,\n            );\n          }\n        }\n        const short = Math.min(width, height);\n        const focus = nearestBlob(front, pointer);\n        if (interactiveRef.current && wakeAmt > 0.001) {\n          const reach = wakeAmt * WAKE_REACH * short;\n          const spread = short * WAKE_SPREAD;\n          for (const b of front) {\n            const dx = pointer.x - b.cx;\n            const dy = pointer.y - b.cy;\n            const dist = Math.hypot(dx, dy) || 1;\n            const fall = Math.exp(-((dist / spread) ** 2));\n            b.cx += (dx / dist) * reach * fall;\n            b.cy += (dy / dist) * reach * fall;\n          }\n        }\n\n        field.setColors(backColors);\n        field.draw({\n          packed: packUniforms(\n            { blobs: back, necks: [], k: kBack },\n            dpr,\n            height,\n          ),\n          k: kBack * dpr,\n          time: elapsed,\n          wobble: BACK_WOBBLE,\n          alpha: 1,\n          hover: null,\n          clear: true,\n        });\n\n        field.setColors(frontColors);\n        field.draw({\n          packed: packUniforms({ blobs: front, necks, k: kFront }, dpr, height),\n          k: kFront * dpr,\n          time: elapsed,\n          wobble: FRONT_WOBBLE,\n          alpha: 1,\n          hover:\n            interactiveRef.current && focus && hoverAmt > 0.01\n              ? packHover(focus, hoverAmt * HOVER_WOBBLE, dpr, height, pointer)\n              : null,\n          clear: false,\n        });\n      };\n\n      const start = performance.now();\n      if (!measure()) return;\n\n      if (still) {\n        draw(stillTime);\n        const observer =\n          typeof ResizeObserver === \"undefined\"\n            ? null\n            : new ResizeObserver(() => {\n                if (measure()) draw(stillTime);\n              });\n        observer?.observe(container);\n        return () => {\n          observer?.disconnect();\n          field.dispose();\n          refreshRef.current = null;\n          canvas.style.width = \"\";\n          canvas.style.height = \"\";\n        };\n      }\n\n      let raf = 0;\n      let elapsed = 0;\n      let last = start;\n      let killed = false;\n      const tick = () => {\n        if (killed) return;\n        const now = performance.now();\n        elapsed += (now - last) / 1000;\n        last = now;\n        hoverAmt += (hoverTarget - hoverAmt) * POINTER_EASE;\n        wakeAmt += (hoverTarget - wakeAmt) * WAKE_EASE;\n        draw(elapsed);\n        raf = requestAnimationFrame(tick);\n      };\n      const stop = () => cancelAnimationFrame(raf);\n      const run = () => {\n        last = performance.now();\n        stop();\n        raf = requestAnimationFrame(tick);\n      };\n\n      const onMove = (event: PointerEvent) => {\n        if (!interactiveRef.current) return;\n        const box = container.getBoundingClientRect();\n        pointer.x = event.clientX - box.left;\n        pointer.y = event.clientY - box.top;\n        hoverTarget = 1;\n      };\n      const onLeave = () => {\n        hoverTarget = 0;\n      };\n      container.addEventListener(\"pointermove\", onMove);\n      container.addEventListener(\"pointerleave\", onLeave);\n\n      const gate = createPauseGate({\n        target: container,\n        onPause: stop,\n        onResume: run,\n      });\n\n      const observer =\n        typeof ResizeObserver === \"undefined\"\n          ? null\n          : new ResizeObserver(() => measure());\n      observer?.observe(container);\n\n      const themeObserver =\n        typeof MutationObserver === \"undefined\"\n          ? null\n          : new MutationObserver(() => refreshColors());\n      themeObserver?.observe(document.documentElement, {\n        attributes: true,\n        attributeFilter: [\"data-theme\", \"class\"],\n      });\n\n      run();\n\n      return () => {\n        killed = true;\n        container.removeEventListener(\"pointermove\", onMove);\n        container.removeEventListener(\"pointerleave\", onLeave);\n        gate.dispose();\n        observer?.disconnect();\n        themeObserver?.disconnect();\n        stop();\n        field.dispose();\n        canvas.style.width = \"\";\n        canvas.style.height = \"\";\n      };\n    }, [fieldOn, still, stillTime, generation]);\n\n    // biome-ignore lint/correctness/useExhaustiveDependencies: `overrides` is not read here, the effect closure reads it live\n    React.useEffect(() => {\n      refreshRef.current?.();\n    }, [overrides]);\n\n    return (\n      <div\n        ref={(node) => {\n          containerRef.current = node;\n          if (typeof forwardedRef === \"function\") forwardedRef(node);\n          else if (forwardedRef) forwardedRef.current = node;\n        }}\n        data-fluid={fieldOn ? \"on\" : \"off\"}\n        className={cn(\"relative isolate overflow-hidden\", className)}\n        {...props}\n      >\n        {fieldOn ? (\n          <div\n            aria-hidden=\"true\"\n            className=\"pointer-events-none absolute inset-0 -z-10\"\n            style={{\n              backgroundImage:\n                \"radial-gradient(120% 90% at 12% 88%, color-mix(in oklab, var(--usva-accent) 7%, transparent), transparent 55%)\",\n            }}\n          >\n            <canvas ref={canvasRef} className=\"block h-full w-full\" />\n          </div>\n        ) : null}\n        {children}\n      </div>\n    );\n  },\n);\nSulaField.displayName = \"SulaField\";\n"
    }
  ]
}