{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "playback-ghost",
  "title": "Playback Ghost",
  "description": "Translucent overlay marking where a canvas object was at a previous timestamp during playback.",
  "dependencies": [
    "@vllnt/ui@^0.3.0"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/playback-ghost/playback-ghost.tsx",
      "content": "\"use client\";\n\nimport type { ComponentPropsWithoutRef, ReactNode } from \"react\";\n\nimport { cn } from \"@vllnt/ui\";\n\n/**\n * Object kind — drives the ghost glyph.\n *\n * @public\n */\nexport type PlaybackGhostKind =\n  | \"agent\"\n  | \"artifact\"\n  | \"input\"\n  | \"output\"\n  | \"run\"\n  | \"task\";\n\nconst KIND_GLYPH: Record<PlaybackGhostKind, string> = {\n  agent: \"◇\",\n  artifact: \"◌\",\n  input: \"↘\",\n  output: \"↗\",\n  run: \"▶\",\n  task: \"▢\",\n};\n\nconst KIND_LABEL: Record<PlaybackGhostKind, string> = {\n  agent: \"Agent\",\n  artifact: \"Artifact\",\n  input: \"Input\",\n  output: \"Output\",\n  run: \"Run\",\n  task: \"Task\",\n};\n\n/**\n * Localizable strings.\n *\n * @public\n */\nexport type PlaybackGhostLabels = {\n  /** Aria-label override. Defaults to `\"Playback ghost\"`. */\n  region?: string;\n};\n\nconst DEFAULT_LABELS = {\n  region: \"Playback ghost\",\n} as const satisfies Required<PlaybackGhostLabels>;\n\n/**\n * Props for {@link PlaybackGhost}.\n *\n * @public\n */\nexport type PlaybackGhostProps = {\n  /** Optional kind glyph + tooltip. */\n  kind?: PlaybackGhostKind;\n  /** Optional label rendered next to the glyph (e.g. id, run name). */\n  label?: ReactNode;\n  /** Localizable strings. */\n  labels?: PlaybackGhostLabels;\n  /** Ghost opacity `0..1`. Defaults to `0.4`. */\n  opacity?: number;\n  /** Ghost size in pixels. Defaults to `40`. */\n  size?: number;\n  /** Center X in canvas pixels. */\n  x: number;\n  /** Center Y in canvas pixels. */\n  y: number;\n} & ComponentPropsWithoutRef<\"div\">;\n\nconst clamp01 = (v: number): number => {\n  if (v < 0) {\n    return 0;\n  }\n  if (v > 1) {\n    return 1;\n  }\n  return v;\n};\n\n/**\n * Translucent overlay marking where a canvas object was at a previous\n * timestamp during state playback. Renders a kind glyph (and optional\n * label) at the historical position so the user can compare the\n * present canvas against earlier state without losing context.\n *\n * Pure presentation; the host computes the historical position from the\n * scrubbed timestamp. The wrapper is `pointer-events: none` so host\n * gestures pass through.\n *\n * @example\n * ```tsx\n * <div className=\"relative h-screen w-screen\">\n *   <Canvas />\n *   <PlaybackGhost x={420} y={180} kind=\"run\" label=\"research-2025\" />\n * </div>\n * ```\n *\n * @public\n */\nexport const PlaybackGhost = ({\n  ref,\n  ...props\n}: PlaybackGhostProps & { ref?: React.Ref<HTMLDivElement> }) => {\n  const {\n    className,\n    kind,\n    label,\n    labels,\n    opacity = 0.4,\n    size = 40,\n    x,\n    y,\n    ...rest\n  } = props;\n  const resolvedLabels = { ...DEFAULT_LABELS, ...labels };\n  const safeOpacity = clamp01(opacity);\n  const safeSize = size < 16 ? 16 : size;\n  const ariaLabel = kind\n    ? `${resolvedLabels.region}: ${KIND_LABEL[kind]}`\n    : resolvedLabels.region;\n  return (\n    <div\n      aria-label={ariaLabel}\n      className={cn(\n        \"pointer-events-none absolute z-10 inline-flex -translate-x-1/2 -translate-y-1/2 items-center justify-center gap-1.5 rounded-md border border-dashed border-border/70 bg-background/40 px-2 py-1 text-xs text-foreground backdrop-blur-sm\",\n        className,\n      )}\n      data-playback-ghost\n      data-playback-kind={kind ?? \"unknown\"}\n      ref={ref}\n      role=\"img\"\n      style={{\n        left: x,\n        minHeight: safeSize,\n        minWidth: safeSize,\n        opacity: safeOpacity,\n        top: y,\n      }}\n      {...rest}\n    >\n      {kind ? (\n        <span aria-hidden=\"true\" className=\"text-muted-foreground\">\n          {KIND_GLYPH[kind]}\n        </span>\n      ) : null}\n      {label ? (\n        <span className=\"truncate\" data-playback-ghost-label>\n          {label}\n        </span>\n      ) : null}\n    </div>\n  );\n};\nPlaybackGhost.displayName = \"PlaybackGhost\";\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component",
  "version": "0.3.0",
  "stability": "stable"
}
