{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "watermark",
  "title": "Watermark",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-vue/components/watermark/Watermark.vue",
      "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from 'vue'\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { cn } from '@/lib/utils'\n\nconst props = withDefaults(\n  defineProps<{\n    class?: HTMLAttributes['class']\n    /** Text content for the watermark. Ignored when `image` is set. */\n    content?: string\n    /** Image URL. When set, repeats the image instead of text. */\n    image?: string\n    /** Rotation angle in degrees. */\n    rotate?: number\n    /** Gap between repeats in pixels (both x and y). */\n    gap?: number\n    /** Opacity 0-1. */\n    opacity?: number\n    /** Font size in pixels (text only). */\n    fontSize?: number\n    /** Font color (text only). */\n    color?: string\n    /** Font family (text only). */\n    fontFamily?: string\n    /** Font weight (text only). */\n    fontWeight?: number | string\n    /** z-index of the overlay. */\n    zIndex?: number\n    /** When true, the overlay captures pointer events (blocks interaction). Default false (pointer-events-none). */\n    interactive?: boolean\n  }>(),\n  {\n    content: '',\n    rotate: -22,\n    gap: 100,\n    opacity: 0.08,\n    fontSize: 16,\n    color: 'currentColor',\n    fontFamily: 'sans-serif',\n    fontWeight: 'normal',\n    zIndex: 9,\n    interactive: false,\n  },\n)\n\nconst containerRef = ref<HTMLElement | null>(null)\nconst width = ref(0)\nconst height = ref(0)\nlet resizeObserver: ResizeObserver | null = null\n\nfunction measure() {\n  if (!containerRef.value) return\n  const rect = containerRef.value.getBoundingClientRect()\n  width.value = rect.width\n  height.value = rect.height\n}\n\nonMounted(() => {\n  measure()\n  if (typeof ResizeObserver !== 'undefined' && containerRef.value) {\n    resizeObserver = new ResizeObserver(() => measure())\n    resizeObserver.observe(containerRef.value)\n  }\n})\n\nonBeforeUnmount(() => {\n  resizeObserver?.disconnect()\n  resizeObserver = null\n})\n\n// Build a tiled SVG data URL that repeats the text/image across a tile of\n// size (gap + contentSize). The SVG is then used as a background-image on\n// the overlay div, rotated to the requested angle.\nconst watermarkUrl = computed(() => {\n  if (!width.value || !height.value) return ''\n  const gap = props.gap\n  const fontSize = props.fontSize\n  const text = props.content || ''\n  const rotate = props.rotate\n\n  if (props.image) {\n    // For images we tile the image at its natural size within the gap.\n    const tile = gap + 100\n    const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${tile}\" height=\"${tile}\" viewBox=\"0 0 ${tile} ${tile}\">\n  <image href=\"${props.image}\" x=\"${gap / 2}\" y=\"${gap / 2}\" width=\"100\" height=\"100\" opacity=\"${props.opacity}\" transform=\"rotate(${rotate} ${tile / 2} ${tile / 2})\"/>\n</svg>`\n    return `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svg)))}`\n  }\n\n  if (!text) return ''\n\n  // Estimate text width — rough heuristic, good enough for tiling.\n  const textWidth = text.length * fontSize * 0.6\n  const tileW = gap + textWidth\n  const tileH = gap + fontSize * 1.5\n\n  const escapedText = text\n    .replace(/&/g, '&amp;')\n    .replace(/</g, '&lt;')\n    .replace(/>/g, '&gt;')\n    .replace(/\"/g, '&quot;')\n    .replace(/'/g, '&#39;')\n\n  const svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${tileW}\" height=\"${tileH}\" viewBox=\"0 0 ${tileW} ${tileH}\">\n  <text x=\"${gap / 2}\" y=\"${gap / 2 + fontSize}\" font-size=\"${fontSize}\" font-family=\"${props.fontFamily}\" font-weight=\"${props.fontWeight}\" fill=\"${props.color}\" opacity=\"${props.opacity}\" transform=\"rotate(${rotate} ${gap / 2} ${gap / 2 + fontSize / 2})\">${escapedText}</text>\n</svg>`\n  return `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svg)))}`\n})\n\nconst overlayStyle = computed(() => ({\n  backgroundImage: watermarkUrl.value ? `url(\"${watermarkUrl.value}\")` : undefined,\n  backgroundRepeat: 'repeat',\n  zIndex: props.zIndex,\n  pointerEvents: props.interactive ? 'auto' : 'none',\n}))\n</script>\n\n<template>\n  <div ref=\"containerRef\" data-uipkge data-slot=\"watermark\" :class=\"cn('relative', props.class)\">\n    <slot />\n    <div\n      data-uipkge\n      data-slot=\"watermark-overlay\"\n      class=\"absolute inset-0 overflow-hidden\"\n      :style=\"overlayStyle\"\n      aria-hidden=\"true\"\n    />\n  </div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/watermark/Watermark.vue"
    },
    {
      "path": "packages/registry-vue/components/watermark/index.ts",
      "content": "export { default as Watermark } from './Watermark.vue'\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/watermark/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Overlay watermark that repeats text or an image across the content area at a configurable angle. Supports rotate angle, gap between repeats, opacity, font size/color/weight, z-index, and pointer-event control. Wrap any content via the default slot.",
  "categories": [
    "display",
    "utility"
  ]
}