{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "utm-campaign-builder",
  "title": "Utm Campaign Builder",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-react/blocks/utm-campaign-builder/UtmCampaignBuilder.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport {\n  Check,\n  Copy,\n  Download,\n  ExternalLink,\n  Globe,\n  Link2,\n  QrCode,\n  RotateCcw,\n  CheckCircle2,\n  Tag,\n  TrendingUp,\n} from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Input } from '@/components/ui/input'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'\nimport { Separator } from '@/components/ui/separator'\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'\n\nexport interface UtmPreset {\n  id: string\n  name: string\n  description: string\n  baseUrl: string\n  source: string\n  medium: string\n  campaign: string\n  term?: string\n  content?: string\n  shortlinkSlug: string\n}\n\nexport interface UtmCampaignData {\n  baseUrl: string\n  source: string\n  medium: string\n  campaign: string\n  term: string\n  content: string\n  fullUrl: string\n  shortlinkUrl: string\n}\n\nexport interface UtmCampaignBuilderProps {\n  className?: string\n  initialPreset?: string\n  initialBaseUrl?: string\n  initialSource?: string\n  initialMedium?: string\n  initialCampaign?: string\n  initialTerm?: string\n  initialContent?: string\n  initialShortlinkDomain?: string\n  initialShortlinkSlug?: string\n  onCopyUrl?: (url: string) => void\n  onCopyShortlink?: (shortlink: string) => void\n  onDownloadQr?: (payload: { url: string; filename: string }) => void\n  onChange?: (data: UtmCampaignData) => void\n}\n\nexport const CAMPAIGN_PRESETS: UtmPreset[] = [\n  {\n    id: 'product_launch',\n    name: 'Product Launch',\n    description: 'SaaS feature launch across social platforms',\n    baseUrl: 'https://uipkge.dev/pricing',\n    source: 'twitter',\n    medium: 'social',\n    campaign: 'launch_2026_q3',\n    term: 'ui_components',\n    content: 'banner_hero_cta',\n    shortlinkSlug: 'q3-launch',\n  },\n  {\n    id: 'weekly_newsletter',\n    name: 'Weekly Newsletter',\n    description: 'Substack / Mailchimp subscriber digest campaign',\n    baseUrl: 'https://uipkge.dev/blog',\n    source: 'newsletter',\n    medium: 'email',\n    campaign: 'weekly_digest_issue_48',\n    term: 'developer_tools',\n    content: 'featured_article_button',\n    shortlinkSlug: 'digest-48',\n  },\n  {\n    id: 'google_search_ads',\n    name: 'Google Search Ads',\n    description: 'Targeted Google Search CPC keyword campaign',\n    baseUrl: 'https://uipkge.dev',\n    source: 'google',\n    medium: 'cpc',\n    campaign: 'search_brand_core_us',\n    term: 'vue_react_components',\n    content: 'headline_v2_price_test',\n    shortlinkSlug: 'google-ads',\n  },\n  {\n    id: 'twitter_promo',\n    name: 'Twitter / X Promo',\n    description: 'Promotional & organic posts on X/Twitter',\n    baseUrl: 'https://uipkge.dev/components',\n    source: 'twitter',\n    medium: 'social',\n    campaign: 'flash_discount_august',\n    term: 'lifetime_deal',\n    content: 'video_card_preview',\n    shortlinkSlug: 'x-promo',\n  },\n]\n\nconst SOURCE_CHIPS = [\n  { label: 'twitter', display: 'Twitter / X' },\n  { label: 'google', display: 'Google' },\n  { label: 'newsletter', display: 'Newsletter' },\n  { label: 'linkedin', display: 'LinkedIn' },\n  { label: 'facebook', display: 'Meta / FB' },\n  { label: 'youtube', display: 'YouTube' },\n]\n\nconst MEDIUM_CHIPS = [\n  { label: 'social', display: 'social' },\n  { label: 'cpc', display: 'cpc' },\n  { label: 'email', display: 'email' },\n  { label: 'referral', display: 'referral' },\n  { label: 'organic_social', display: 'organic_social' },\n  { label: 'display', display: 'display' },\n]\n\ninterface QueryParamItem {\n  key: string\n  value: string\n  label: string\n  required: boolean\n}\n\n// Standalone Pure QR Matrix Generator (Byte mode, standard GF(256) Reed-Solomon)\nfunction generateQrMatrix(text: string): { matrix: boolean[][]; size: number } {\n  const bytes: number[] = []\n  for (let i = 0; i < text.length; i++) {\n    const code = text.charCodeAt(i)\n    if (code < 0x80) bytes.push(code)\n    else if (code < 0x800) {\n      bytes.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f))\n    } else {\n      bytes.push(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f))\n    }\n  }\n\n  const versions = [\n    { version: 1, size: 21, dataCap: 16, ecWords: 10, blocks: 1, alignPos: [] as number[] },\n    { version: 2, size: 25, dataCap: 28, ecWords: 16, blocks: 1, alignPos: [6, 18] },\n    { version: 3, size: 29, dataCap: 44, ecWords: 26, blocks: 1, alignPos: [6, 22] },\n    { version: 4, size: 33, dataCap: 64, ecWords: 18, blocks: 2, alignPos: [6, 26] },\n    { version: 5, size: 37, dataCap: 86, ecWords: 24, blocks: 2, alignPos: [6, 30] },\n    { version: 6, size: 41, dataCap: 108, ecWords: 16, blocks: 4, alignPos: [6, 34] },\n    { version: 7, size: 45, dataCap: 124, ecWords: 18, blocks: 4, alignPos: [6, 22, 38] },\n    { version: 8, size: 49, dataCap: 154, ecWords: 22, blocks: 4, alignPos: [6, 24, 42] },\n    { version: 9, size: 53, dataCap: 182, ecWords: 22, blocks: 5, alignPos: [6, 26, 46] },\n    { version: 10, size: 57, dataCap: 216, ecWords: 26, blocks: 5, alignPos: [6, 28, 50] },\n  ]\n\n  let v = versions.find((spec) => bytes.length <= spec.dataCap - 3)\n  if (!v) v = versions[versions.length - 1]\n\n  const size = v.size\n  const matrix: (boolean | null)[][] = Array.from({ length: size }, () => Array(size).fill(null))\n  const isFunction: boolean[][] = Array.from({ length: size }, () => Array(size).fill(false))\n\n  function placeFinder(r: number, c: number) {\n    for (let dr = -1; dr <= 7; dr++) {\n      for (let dc = -1; dc <= 7; dc++) {\n        const nr = r + dr\n        const nc = c + dc\n        if (nr >= 0 && nr < size && nc >= 0 && nc < size) {\n          isFunction[nr][nc] = true\n          if (dr >= 0 && dr <= 6 && dc >= 0 && dc <= 6) {\n            const isBorder = dr === 0 || dr === 6 || dc === 0 || dc === 6\n            const isCenter = dr >= 2 && dr <= 4 && dc >= 2 && dc <= 4\n            matrix[nr][nc] = isBorder || isCenter\n          } else {\n            matrix[nr][nc] = false\n          }\n        }\n      }\n    }\n  }\n\n  placeFinder(0, 0)\n  placeFinder(0, size - 7)\n  placeFinder(size - 7, 0)\n\n  for (const ar of v.alignPos) {\n    for (const ac of v.alignPos) {\n      if ((ar === 6 && ac === 6) || (ar === 6 && ac === size - 7) || (ar === size - 7 && ac === 6)) {\n        continue\n      }\n      if (isFunction[ar][ac]) continue\n\n      for (let dr = -2; dr <= 2; dr++) {\n        for (let dc = -2; dc <= 2; dc++) {\n          const nr = ar + dr\n          const nc = ac + dc\n          isFunction[nr][nc] = true\n          const isBorder = Math.abs(dr) === 2 || Math.abs(dc) === 2\n          const isCenter = dr === 0 && dc === 0\n          matrix[nr][nc] = isBorder || isCenter\n        }\n      }\n    }\n  }\n\n  for (let i = 0; i < size; i++) {\n    if (!isFunction[6][i]) {\n      isFunction[6][i] = true\n      matrix[6][i] = i % 2 === 0\n    }\n    if (!isFunction[i][6]) {\n      isFunction[i][6] = true\n      matrix[i][6] = i % 2 === 0\n    }\n  }\n\n  isFunction[4 * v.version + 9][8] = true\n  matrix[4 * v.version + 9][8] = true\n\n  for (let i = 0; i < 9; i++) {\n    if (i !== 6) {\n      isFunction[8][i] = true\n      isFunction[i][8] = true\n    }\n  }\n  for (let i = 0; i < 8; i++) {\n    isFunction[8][size - 1 - i] = true\n    isFunction[size - 1 - i][8] = true\n  }\n\n  const bitStream: number[] = []\n  function pushBits(val: number, len: number) {\n    for (let i = len - 1; i >= 0; i--) {\n      bitStream.push((val >> i) & 1)\n    }\n  }\n\n  pushBits(0b0100, 4)\n  pushBits(bytes.length, 8)\n  for (const b of bytes) {\n    pushBits(b, 8)\n  }\n\n  const totalDataBits = v.dataCap * 8\n  const termLen = Math.min(4, totalDataBits - bitStream.length)\n  pushBits(0, termLen)\n\n  while (bitStream.length % 8 !== 0) {\n    bitStream.push(0)\n  }\n\n  const padBytes = [0xec, 0x11]\n  let padIdx = 0\n  while (bitStream.length < totalDataBits) {\n    pushBits(padBytes[padIdx % 2], 8)\n    padIdx++\n  }\n\n  const dataWords: number[] = []\n  for (let i = 0; i < bitStream.length; i += 8) {\n    let word = 0\n    for (let j = 0; j < 8; j++) {\n      word = (word << 1) | bitStream[i + j]\n    }\n    dataWords.push(word)\n  }\n\n  const EXP: number[] = new Array(512)\n  const LOG: number[] = new Array(256)\n  let cur = 1\n  for (let i = 0; i < 255; i++) {\n    EXP[i] = cur\n    EXP[i + 255] = cur\n    LOG[cur] = i\n    cur = (cur << 1) ^ (cur >= 128 ? 0x11d : 0)\n  }\n  function gfMul(a: number, b: number): number {\n    if (a === 0 || b === 0) return 0\n    return EXP[LOG[a] + LOG[b]]\n  }\n\n  function getGeneratorPoly(numEc: number): number[] {\n    let poly = [1]\n    for (let i = 0; i < numEc; i++) {\n      const next = new Array(poly.length + 1).fill(0)\n      for (let j = 0; j < poly.length; j++) {\n        next[j] ^= gfMul(poly[j], EXP[i])\n        next[j + 1] ^= poly[j]\n      }\n      poly = next\n    }\n    return poly\n  }\n\n  const ecWordsPerBlock = v.ecWords\n  const numBlocks = v.blocks\n  const blockSize = Math.floor(dataWords.length / numBlocks)\n  const rawBlocks: number[][] = []\n  const ecBlocks: number[][] = []\n  const genPoly = getGeneratorPoly(ecWordsPerBlock)\n\n  for (let b = 0; b < numBlocks; b++) {\n    const blockData = dataWords.slice(b * blockSize, (b + 1) * blockSize)\n    rawBlocks.push(blockData)\n\n    const rem = new Array(blockData.length + ecWordsPerBlock).fill(0)\n    for (let i = 0; i < blockData.length; i++) rem[i] = blockData[i]\n    for (let i = 0; i < blockData.length; i++) {\n      const factor = rem[i]\n      if (factor !== 0) {\n        for (let j = 0; j < genPoly.length; j++) {\n          rem[i + j] ^= gfMul(genPoly[j], factor)\n        }\n      }\n    }\n    ecBlocks.push(rem.slice(blockData.length))\n  }\n\n  const finalBits: number[] = []\n  for (let i = 0; i < blockSize; i++) {\n    for (let b = 0; b < numBlocks; b++) {\n      const val = rawBlocks[b][i] ?? 0\n      for (let bit = 7; bit >= 0; bit--) {\n        finalBits.push((val >> bit) & 1)\n      }\n    }\n  }\n  for (let i = 0; i < ecWordsPerBlock; i++) {\n    for (let b = 0; b < numBlocks; b++) {\n      const val = ecBlocks[b][i] ?? 0\n      for (let bit = 7; bit >= 0; bit--) {\n        finalBits.push((val >> bit) & 1)\n      }\n    }\n  }\n\n  let bitPointer = 0\n  let right = size - 1\n  let upward = true\n\n  while (right > 0) {\n    if (right === 6) right--\n    const colList = [right, right - 1]\n    const rowList = upward\n      ? Array.from({ length: size }, (_, i) => size - 1 - i)\n      : Array.from({ length: size }, (_, i) => i)\n\n    for (const r of rowList) {\n      for (const c of colList) {\n        if (!isFunction[r][c]) {\n          let bit = 0\n          if (bitPointer < finalBits.length) {\n            bit = finalBits[bitPointer++]\n          }\n          const mask = (r + c) % 2 === 0\n          matrix[r][c] = (bit === 1) !== mask\n        }\n      }\n    }\n    right -= 2\n    upward = !upward\n  }\n\n  const formatInfo = [1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0]\n  const tlCoords: [number, number][] = [\n    [8, 0],\n    [8, 1],\n    [8, 2],\n    [8, 3],\n    [8, 4],\n    [8, 5],\n    [8, 7],\n    [8, 8],\n    [7, 8],\n    [5, 8],\n    [4, 8],\n    [3, 8],\n    [2, 8],\n    [1, 8],\n    [0, 8],\n  ]\n  for (let i = 0; i < 15; i++) {\n    const [r, c] = tlCoords[i]\n    matrix[r][c] = formatInfo[i] === 1\n  }\n\n  for (let i = 0; i < 8; i++) {\n    matrix[8][size - 1 - i] = formatInfo[i] === 1\n  }\n  for (let i = 8; i < 15; i++) {\n    matrix[size - (15 - i)][8] = formatInfo[i] === 1\n  }\n\n  const cleanMatrix: boolean[][] = matrix.map((row) => row.map((cell) => cell === true))\n  return { matrix: cleanMatrix, size }\n}\n\nfunction qrToSvgPath(matrix: boolean[][]): string {\n  let path = ''\n  for (let r = 0; r < matrix.length; r++) {\n    for (let c = 0; c < matrix[r].length; c++) {\n      if (matrix[r][c]) {\n        path += `M${c},${r}h1v1h-1z `\n      }\n    }\n  }\n  return path\n}\n\nexport function UtmCampaignBuilder({\n  className,\n  initialPreset = 'product_launch',\n  initialBaseUrl = 'https://uipkge.dev/pricing',\n  initialSource = 'twitter',\n  initialMedium = 'social',\n  initialCampaign = 'launch_2026_q3',\n  initialTerm = 'ui_components',\n  initialContent = 'banner_hero_cta',\n  initialShortlinkDomain = 'https://uipk.ge',\n  initialShortlinkSlug = 'q3-launch',\n  onCopyUrl,\n  onCopyShortlink,\n  onDownloadQr,\n  onChange,\n}: UtmCampaignBuilderProps) {\n  const [selectedPreset, setSelectedPreset] = React.useState(initialPreset)\n  const [baseUrl, setBaseUrl] = React.useState(initialBaseUrl)\n  const [utmSource, setUtmSource] = React.useState(initialSource)\n  const [utmMedium, setUtmMedium] = React.useState(initialMedium)\n  const [utmCampaign, setUtmCampaign] = React.useState(initialCampaign)\n  const [utmTerm, setUtmTerm] = React.useState(initialTerm)\n  const [utmContent, setUtmContent] = React.useState(initialContent)\n\n  const [shortlinkDomain] = React.useState(initialShortlinkDomain)\n  const [shortlinkSlug, setShortlinkSlug] = React.useState(initialShortlinkSlug)\n\n  const [copiedFullUrl, setCopiedFullUrl] = React.useState(false)\n  const [copiedShortlink, setCopiedShortlink] = React.useState(false)\n  const [downloadedQr, setDownloadedQr] = React.useState(false)\n\n  const applyPreset = React.useCallback((presetId: string) => {\n    setSelectedPreset(presetId)\n    if (presetId === 'custom') return\n\n    const preset = CAMPAIGN_PRESETS.find((p) => p.id === presetId)\n    if (preset) {\n      setBaseUrl(preset.baseUrl)\n      setUtmSource(preset.source)\n      setUtmMedium(preset.medium)\n      setUtmCampaign(preset.campaign)\n      setUtmTerm(preset.term ?? '')\n      setUtmContent(preset.content ?? '')\n      setShortlinkSlug(preset.shortlinkSlug)\n    }\n  }, [])\n\n  const handleReset = React.useCallback(() => {\n    setSelectedPreset('product_launch')\n    const def = CAMPAIGN_PRESETS[0]\n    setBaseUrl(def.baseUrl)\n    setUtmSource(def.source)\n    setUtmMedium(def.medium)\n    setUtmCampaign(def.campaign)\n    setUtmTerm(def.term ?? '')\n    setUtmContent(def.content ?? '')\n    setShortlinkSlug(def.shortlinkSlug)\n  }, [])\n\n  // Active query parameters\n  const activeQueryParameters = React.useMemo<QueryParamItem[]>(() => {\n    const list: QueryParamItem[] = []\n    if (utmSource.trim()) {\n      list.push({\n        key: 'utm_source',\n        value: utmSource.trim(),\n        label: 'Campaign Source',\n        required: true,\n      })\n    }\n    if (utmMedium.trim()) {\n      list.push({\n        key: 'utm_medium',\n        value: utmMedium.trim(),\n        label: 'Campaign Medium',\n        required: true,\n      })\n    }\n    if (utmCampaign.trim()) {\n      list.push({\n        key: 'utm_campaign',\n        value: utmCampaign.trim(),\n        label: 'Campaign Name',\n        required: true,\n      })\n    }\n    if (utmTerm.trim()) {\n      list.push({\n        key: 'utm_term',\n        value: utmTerm.trim(),\n        label: 'Campaign Term',\n        required: false,\n      })\n    }\n    if (utmContent.trim()) {\n      list.push({\n        key: 'utm_content',\n        value: utmContent.trim(),\n        label: 'Campaign Content',\n        required: false,\n      })\n    }\n    return list\n  }, [utmSource, utmMedium, utmCampaign, utmTerm, utmContent])\n\n  // Full Generated URL\n  const generatedFullUrl = React.useMemo(() => {\n    const rawBase = baseUrl.trim()\n    if (!rawBase) return ''\n\n    let normalized = rawBase\n    if (!/^https?:\\/\\//i.test(normalized)) {\n      normalized = `https://${normalized}`\n    }\n\n    const params = new URLSearchParams()\n    if (utmSource.trim()) params.set('utm_source', utmSource.trim())\n    if (utmMedium.trim()) params.set('utm_medium', utmMedium.trim())\n    if (utmCampaign.trim()) params.set('utm_campaign', utmCampaign.trim())\n    if (utmTerm.trim()) params.set('utm_term', utmTerm.trim())\n    if (utmContent.trim()) params.set('utm_content', utmContent.trim())\n\n    const queryString = params.toString()\n    if (!queryString) return normalized\n\n    const separator = normalized.includes('?') ? '&' : '?'\n    return `${normalized}${separator}${queryString}`\n  }, [baseUrl, utmSource, utmMedium, utmCampaign, utmTerm, utmContent])\n\n  // Shortlink preview\n  const generatedShortlink = React.useMemo(() => {\n    const domain = shortlinkDomain.replace(/\\/+$/, '')\n    const slug =\n      shortlinkSlug.trim() ||\n      utmCampaign\n        .trim()\n        .toLowerCase()\n        .replace(/[^a-z0-9]+/g, '-') ||\n      'link'\n    return `${domain}/${slug}`\n  }, [shortlinkDomain, shortlinkSlug, utmCampaign])\n\n  const isGa4Compliant = Boolean(baseUrl.trim() && utmSource.trim() && utmMedium.trim() && utmCampaign.trim())\n\n  React.useEffect(() => {\n    onChange?.({\n      baseUrl,\n      source: utmSource,\n      medium: utmMedium,\n      campaign: utmCampaign,\n      term: utmTerm,\n      content: utmContent,\n      fullUrl: generatedFullUrl,\n      shortlinkUrl: generatedShortlink,\n    })\n  }, [baseUrl, utmSource, utmMedium, utmCampaign, utmTerm, utmContent, generatedFullUrl, generatedShortlink, onChange])\n\n  const handleCopyFullUrl = async () => {\n    const url = generatedFullUrl\n    if (!url) return\n\n    try {\n      if (navigator.clipboard && window.isSecureContext) {\n        await navigator.clipboard.writeText(url)\n      } else {\n        const textarea = document.createElement('textarea')\n        textarea.value = url\n        textarea.style.position = 'fixed'\n        textarea.style.opacity = '0'\n        document.body.appendChild(textarea)\n        textarea.focus()\n        textarea.select()\n        document.execCommand('copy')\n        document.body.removeChild(textarea)\n      }\n\n      setCopiedFullUrl(true)\n      onCopyUrl?.(url)\n      setTimeout(() => setCopiedFullUrl(false), 2000)\n    } catch (err) {\n      console.error('Failed to copy URL:', err)\n    }\n  }\n\n  const handleCopyShortlink = async () => {\n    const link = generatedShortlink\n    if (!link) return\n\n    try {\n      if (navigator.clipboard && window.isSecureContext) {\n        await navigator.clipboard.writeText(link)\n      } else {\n        const textarea = document.createElement('textarea')\n        textarea.value = link\n        textarea.style.position = 'fixed'\n        textarea.style.opacity = '0'\n        document.body.appendChild(textarea)\n        textarea.focus()\n        textarea.select()\n        document.execCommand('copy')\n        document.body.removeChild(textarea)\n      }\n\n      setCopiedShortlink(true)\n      onCopyShortlink?.(link)\n      setTimeout(() => setCopiedShortlink(false), 2000)\n    } catch (err) {\n      console.error('Failed to copy shortlink:', err)\n    }\n  }\n\n  const qrData = React.useMemo(() => {\n    const text = generatedFullUrl || 'https://uipkge.dev'\n    return generateQrMatrix(text)\n  }, [generatedFullUrl])\n\n  const qrPath = React.useMemo(() => qrToSvgPath(qrData.matrix), [qrData])\n\n  const handleDownloadQr = () => {\n    const text = generatedFullUrl || 'https://uipkge.dev'\n    const { matrix, size } = generateQrMatrix(text)\n    const pathData = qrToSvgPath(matrix)\n    const margin = 2\n    const totalSize = size + margin * 2\n    const filename = utmCampaign.trim() ? `${utmCampaign.trim()}-qr.svg` : 'utm-campaign-qr.svg'\n\n    const svgContent = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 ${totalSize} ${totalSize}\" width=\"512\" height=\"512\">\n  <rect width=\"100%\" height=\"100%\" fill=\"#ffffff\" />\n  <g transform=\"translate(${margin}, ${margin})\">\n    <path d=\"${pathData}\" fill=\"#0f172a\" shape-rendering=\"crispEdges\" />\n  </g>\n</svg>`\n\n    const blob = new Blob([svgContent], { type: 'image/svg+xml;charset=utf-8' })\n    const url = URL.createObjectURL(blob)\n    const link = document.createElement('a')\n    link.href = url\n    link.download = filename\n    document.body.appendChild(link)\n    link.click()\n    document.body.removeChild(link)\n    URL.revokeObjectURL(url)\n\n    setDownloadedQr(true)\n    onDownloadQr?.({ url: text, filename })\n    setTimeout(() => setDownloadedQr(false), 2000)\n  }\n\n  return (\n    <div data-slot=\"utm-campaign-builder\" className={cn('mx-auto w-full max-w-7xl space-y-6', className)}>\n      {/* Header Section */}\n      <div className=\"border-border flex flex-col gap-4 border-b pb-5 sm:flex-row sm:items-center sm:justify-between\">\n        <div className=\"space-y-1\">\n          <div className=\"flex flex-wrap items-center gap-2.5\">\n            <div className=\"bg-primary/10 text-primary border-primary/20 flex size-8 items-center justify-center rounded-md border\">\n              <TrendingUp className=\"size-4\" aria-hidden=\"true\" />\n            </div>\n            <h1 className=\"text-foreground text-xl font-bold tracking-tight sm:text-2xl\">Campaign URL & UTM Builder</h1>\n            <Badge variant={isGa4Compliant ? 'default' : 'outline'} className=\"gap-1 px-2 py-0.5 text-xs font-semibold\">\n              <CheckCircle2 className=\"size-3\" aria-hidden=\"true\" />\n              {isGa4Compliant ? 'GA4 Standard' : 'Draft Link'}\n            </Badge>\n          </div>\n          <p className=\"text-muted-foreground text-xs sm:text-sm\">\n            Generate trackable marketing links with standardized GA4 parameters, shortlinks, and vector QR codes.\n          </p>\n        </div>\n\n        {/* Preset Selector & Reset Action */}\n        <div className=\"flex flex-wrap items-center gap-2\">\n          <div className=\"w-56\">\n            <Select value={selectedPreset} onValueChange={applyPreset}>\n              <SelectTrigger size=\"sm\" className=\"h-9 text-xs\">\n                <SelectValue placeholder=\"Select campaign preset\" />\n              </SelectTrigger>\n              <SelectContent>\n                {CAMPAIGN_PRESETS.map((preset) => (\n                  <SelectItem key={preset.id} value={preset.id}>\n                    {preset.name}\n                  </SelectItem>\n                ))}\n                <SelectItem value=\"custom\">Custom Parameters</SelectItem>\n              </SelectContent>\n            </Select>\n          </div>\n\n          <Button variant=\"outline\" size=\"sm\" className=\"h-9 gap-1.5 text-xs\" onClick={handleReset}>\n            <RotateCcw className=\"size-3.5\" aria-hidden=\"true\" />\n            Reset\n          </Button>\n        </div>\n      </div>\n\n      {/* 2-Column Builder Layout */}\n      <div className=\"grid grid-cols-1 items-start gap-8 lg:grid-cols-12\">\n        {/* Left Column: Parameter Inputs Form */}\n        <div className=\"space-y-6 lg:col-span-7\">\n          <Card>\n            <CardHeader className=\"pb-4\">\n              <div className=\"flex items-center justify-between\">\n                <div className=\"flex items-center gap-2\">\n                  <Link2 className=\"text-primary size-4\" aria-hidden=\"true\" />\n                  <CardTitle className=\"text-base font-semibold\">Campaign Parameters</CardTitle>\n                </div>\n                <Badge variant=\"secondary\" className=\"font-mono text-xs\">\n                  {activeQueryParameters.length} active params\n                </Badge>\n              </div>\n              <CardDescription className=\"text-xs\">\n                Fill in the landing page destination and key attribution parameters for Google Analytics 4.\n              </CardDescription>\n            </CardHeader>\n\n            <CardContent className=\"space-y-5\">\n              {/* 1. Base Website URL */}\n              <div className=\"space-y-1.5\">\n                <div className=\"flex items-center justify-between\">\n                  <label htmlFor=\"utm-base-url\" className=\"text-foreground text-xs font-medium\">\n                    Website URL <span className=\"text-destructive\">*</span>\n                  </label>\n                  <span className=\"text-muted-foreground text-xs\">Destination link</span>\n                </div>\n                <Input\n                  id=\"utm-base-url\"\n                  value={baseUrl}\n                  onChange={(e) => setBaseUrl(e.target.value)}\n                  type=\"url\"\n                  placeholder=\"https://uipkge.dev/pricing\"\n                  size=\"middle\"\n                />\n                <p className=\"text-muted-foreground text-xs\">The full landing page URL where users will arrive.</p>\n              </div>\n\n              <Separator />\n\n              {/* 2. Campaign Source (utm_source) */}\n              <div className=\"space-y-2\">\n                <div className=\"flex items-center justify-between\">\n                  <div className=\"flex items-center gap-1.5\">\n                    <label htmlFor=\"utm-source\" className=\"text-foreground text-xs font-medium\">\n                      Campaign Source <span className=\"text-destructive\">*</span>\n                    </label>\n                    <code className=\"bg-muted text-muted-foreground rounded px-1.5 py-0.5 font-mono text-xs\">\n                      utm_source\n                    </code>\n                  </div>\n                  <Badge variant=\"outline\" className=\"text-xs\">\n                    Required\n                  </Badge>\n                </div>\n                <Input\n                  id=\"utm-source\"\n                  value={utmSource}\n                  onChange={(e) => setUtmSource(e.target.value)}\n                  placeholder=\"e.g. twitter, google, newsletter\"\n                  size=\"middle\"\n                />\n                {/* Quick Chips */}\n                <div className=\"flex flex-wrap items-center gap-1.5 pt-1\">\n                  <span className=\"text-muted-foreground mr-1 text-xs\">Suggestions:</span>\n                  {SOURCE_CHIPS.map((chip) => (\n                    <button\n                      key={chip.label}\n                      type=\"button\"\n                      className={cn(\n                        'min-h-6 rounded-md border px-2 py-0.5 text-xs font-medium transition-colors',\n                        utmSource === chip.label\n                          ? 'border-primary bg-primary/10 text-primary font-semibold'\n                          : 'border-border bg-muted/40 text-muted-foreground hover:bg-muted hover:text-foreground',\n                      )}\n                      onClick={() => setUtmSource(chip.label)}\n                    >\n                      {chip.label}\n                    </button>\n                  ))}\n                </div>\n              </div>\n\n              {/* 3. Campaign Medium (utm_medium) */}\n              <div className=\"space-y-2\">\n                <div className=\"flex items-center justify-between\">\n                  <div className=\"flex items-center gap-1.5\">\n                    <label htmlFor=\"utm-medium\" className=\"text-foreground text-xs font-medium\">\n                      Campaign Medium <span className=\"text-destructive\">*</span>\n                    </label>\n                    <code className=\"bg-muted text-muted-foreground rounded px-1.5 py-0.5 font-mono text-xs\">\n                      utm_medium\n                    </code>\n                  </div>\n                  <Badge variant=\"outline\" className=\"text-xs\">\n                    Required\n                  </Badge>\n                </div>\n                <Input\n                  id=\"utm-medium\"\n                  value={utmMedium}\n                  onChange={(e) => setUtmMedium(e.target.value)}\n                  placeholder=\"e.g. social, cpc, email, referral\"\n                  size=\"middle\"\n                />\n                {/* Quick Chips */}\n                <div className=\"flex flex-wrap items-center gap-1.5 pt-1\">\n                  <span className=\"text-muted-foreground mr-1 text-xs\">Suggestions:</span>\n                  {MEDIUM_CHIPS.map((chip) => (\n                    <button\n                      key={chip.label}\n                      type=\"button\"\n                      className={cn(\n                        'min-h-6 rounded-md border px-2 py-0.5 text-xs font-medium transition-colors',\n                        utmMedium === chip.label\n                          ? 'border-primary bg-primary/10 text-primary font-semibold'\n                          : 'border-border bg-muted/40 text-muted-foreground hover:bg-muted hover:text-foreground',\n                      )}\n                      onClick={() => setUtmMedium(chip.label)}\n                    >\n                      {chip.label}\n                    </button>\n                  ))}\n                </div>\n              </div>\n\n              {/* 4. Campaign Name (utm_campaign) */}\n              <div className=\"space-y-1.5\">\n                <div className=\"flex items-center justify-between\">\n                  <div className=\"flex items-center gap-1.5\">\n                    <label htmlFor=\"utm-campaign\" className=\"text-foreground text-xs font-medium\">\n                      Campaign Name <span className=\"text-destructive\">*</span>\n                    </label>\n                    <code className=\"bg-muted text-muted-foreground rounded px-1.5 py-0.5 font-mono text-xs\">\n                      utm_campaign\n                    </code>\n                  </div>\n                  <Badge variant=\"outline\" className=\"text-xs\">\n                    Required\n                  </Badge>\n                </div>\n                <Input\n                  id=\"utm-campaign\"\n                  value={utmCampaign}\n                  onChange={(e) => setUtmCampaign(e.target.value)}\n                  placeholder=\"e.g. launch_2026_q3, summer_promo\"\n                  size=\"middle\"\n                />\n                <p className=\"text-muted-foreground text-xs\">\n                  Unique promotion identifier, product slogan, or promo code.\n                </p>\n              </div>\n\n              <Separator />\n\n              {/* Optional Parameters Section Header */}\n              <div className=\"flex items-center gap-2 pt-1\">\n                <Tag className=\"text-muted-foreground size-3.5\" aria-hidden=\"true\" />\n                <span className=\"text-muted-foreground text-xs font-semibold tracking-wider uppercase\">\n                  Optional GA4 Attributes\n                </span>\n              </div>\n\n              {/* 5. Campaign Term (utm_term) */}\n              <div className=\"space-y-1.5\">\n                <div className=\"flex items-center justify-between\">\n                  <div className=\"flex items-center gap-1.5\">\n                    <label htmlFor=\"utm-term\" className=\"text-foreground text-xs font-medium\">\n                      Campaign Term\n                    </label>\n                    <code className=\"bg-muted text-muted-foreground rounded px-1.5 py-0.5 font-mono text-xs\">\n                      utm_term\n                    </code>\n                  </div>\n                  <span className=\"text-muted-foreground text-xs\">Optional · Paid search keywords</span>\n                </div>\n                <Input\n                  id=\"utm-term\"\n                  value={utmTerm}\n                  onChange={(e) => setUtmTerm(e.target.value)}\n                  placeholder=\"e.g. ui_components, vue_dashboard\"\n                  size=\"middle\"\n                />\n              </div>\n\n              {/* 6. Campaign Content (utm_content) */}\n              <div className=\"space-y-1.5\">\n                <div className=\"flex items-center justify-between\">\n                  <div className=\"flex items-center gap-1.5\">\n                    <label htmlFor=\"utm-content\" className=\"text-foreground text-xs font-medium\">\n                      Campaign Content\n                    </label>\n                    <code className=\"bg-muted text-muted-foreground rounded px-1.5 py-0.5 font-mono text-xs\">\n                      utm_content\n                    </code>\n                  </div>\n                  <span className=\"text-muted-foreground text-xs\">Optional · A/B test & ad creative</span>\n                </div>\n                <Input\n                  id=\"utm-content\"\n                  value={utmContent}\n                  onChange={(e) => setUtmContent(e.target.value)}\n                  placeholder=\"e.g. banner_cta_blue, sidebar_link_v2\"\n                  size=\"middle\"\n                />\n              </div>\n            </CardContent>\n          </Card>\n        </div>\n\n        {/* Right Column: Generated Campaign URL Card (Sticky) */}\n        <div className=\"space-y-5 lg:sticky lg:top-6 lg:col-span-5\">\n          <Card className=\"border-border overflow-hidden shadow-sm\">\n            <CardHeader className=\"pb-3\">\n              <div className=\"flex items-center justify-between\">\n                <div className=\"flex items-center gap-2\">\n                  <span className=\"relative flex size-2\">\n                    <span className=\"bg-success absolute inline-flex h-full w-full rounded-full opacity-75\" />\n                    <span className=\"bg-success relative inline-flex size-2 rounded-full\" />\n                  </span>\n                  <CardTitle className=\"text-base font-semibold\">Generated Campaign URL</CardTitle>\n                </div>\n                <Badge variant=\"outline\" className=\"border-success/30 bg-success/10 text-success text-xs font-medium\">\n                  Live Ready\n                </Badge>\n              </div>\n              <CardDescription className=\"text-xs\">\n                Formatted destination URL with tracking query parameters.\n              </CardDescription>\n            </CardHeader>\n\n            <CardContent className=\"space-y-4\">\n              {/* Monospace Syntax-Highlighted Code Container */}\n              <div className=\"bg-muted/70 border-border/80 relative rounded-lg border p-3.5 font-mono text-xs leading-relaxed break-all select-all\">\n                {generatedFullUrl ? (\n                  <div>\n                    <span className=\"text-foreground font-semibold\">{baseUrl.trim()}</span>\n                    {activeQueryParameters.length > 0 && (\n                      <>\n                        <span className=\"text-muted-foreground font-bold\">{baseUrl.includes('?') ? '&' : '?'}</span>\n                        {activeQueryParameters.map((param, index) => (\n                          <React.Fragment key={param.key}>\n                            <span className=\"text-info font-medium\">{param.key}</span>\n                            <span className=\"text-muted-foreground\">=</span>\n                            <span className=\"text-success font-semibold\">{encodeURIComponent(param.value)}</span>\n                            {index < activeQueryParameters.length - 1 && (\n                              <span className=\"text-muted-foreground font-bold\">&amp;</span>\n                            )}\n                          </React.Fragment>\n                        ))}\n                      </>\n                    )}\n                  </div>\n                ) : (\n                  <div className=\"text-muted-foreground italic\">\n                    Enter a destination website URL to generate your campaign link...\n                  </div>\n                )}\n              </div>\n\n              {/* Action Buttons */}\n              <div className=\"grid grid-cols-1 gap-2 sm:grid-cols-2\">\n                <Button\n                  size=\"sm\"\n                  className=\"w-full gap-1.5 text-xs font-semibold\"\n                  disabled={!generatedFullUrl}\n                  onClick={handleCopyFullUrl}\n                >\n                  {copiedFullUrl ? (\n                    <Check className=\"text-success size-3.5\" aria-hidden=\"true\" />\n                  ) : (\n                    <Copy className=\"size-3.5\" aria-hidden=\"true\" />\n                  )}\n                  {copiedFullUrl ? 'Copied Full URL!' : 'Copy Full URL'}\n                </Button>\n\n                <Button\n                  variant=\"outline\"\n                  size=\"sm\"\n                  className=\"w-full gap-1.5 text-xs\"\n                  disabled={!generatedFullUrl}\n                  asChild\n                >\n                  <a href={generatedFullUrl || undefined} target=\"_blank\" rel=\"noopener noreferrer\">\n                    <ExternalLink className=\"size-3.5\" aria-hidden=\"true\" />\n                    Test in New Tab\n                  </a>\n                </Button>\n              </div>\n\n              <Separator />\n\n              {/* Shortlink Generator Preview */}\n              <div className=\"space-y-2\">\n                <div className=\"flex items-center justify-between\">\n                  <div className=\"flex items-center gap-1.5\">\n                    <Globe className=\"text-primary size-3.5\" aria-hidden=\"true\" />\n                    <span className=\"text-foreground text-xs font-semibold\">Shortlink Generator</span>\n                  </div>\n                  <span className=\"text-muted-foreground text-xs\">For SMS & Social</span>\n                </div>\n\n                <div className=\"flex items-center gap-2\">\n                  <div className=\"relative flex-1\">\n                    <Input\n                      value={shortlinkSlug}\n                      onChange={(e) => setShortlinkSlug(e.target.value)}\n                      placeholder=\"custom-slug\"\n                      size=\"small\"\n                      prefix={<span className=\"text-muted-foreground font-mono text-xs\">uipk.ge/</span>}\n                      className=\"font-mono text-xs\"\n                    />\n                  </div>\n                  <Button variant=\"outline\" size=\"sm\" className=\"shrink-0 gap-1 text-xs\" onClick={handleCopyShortlink}>\n                    {copiedShortlink ? (\n                      <Check className=\"text-success size-3\" aria-hidden=\"true\" />\n                    ) : (\n                      <Copy className=\"size-3\" aria-hidden=\"true\" />\n                    )}\n                    {copiedShortlink ? 'Copied!' : 'Copy'}\n                  </Button>\n                </div>\n              </div>\n\n              <Separator />\n\n              {/* SVG QR Code Generator Pass */}\n              <div className=\"space-y-3\">\n                <div className=\"flex items-center justify-between\">\n                  <div className=\"flex items-center gap-1.5\">\n                    <QrCode className=\"text-primary size-3.5\" aria-hidden=\"true\" />\n                    <span className=\"text-foreground text-xs font-semibold\">QR Code Campaign Pass</span>\n                  </div>\n                  <Badge variant=\"outline\" className=\"text-xs\">\n                    Print & Banners\n                  </Badge>\n                </div>\n\n                <div className=\"bg-muted/40 border-border/70 flex flex-col items-center justify-center gap-3 rounded-lg border p-4 text-center\">\n                  {/* Crisp Vector SVG QR Code */}\n                  <div className=\"bg-card text-foreground border-border flex size-36 items-center justify-center rounded-md border p-2 shadow-xs\">\n                    <svg\n                      xmlns=\"http://www.w3.org/2000/svg\"\n                      viewBox={`0 0 ${qrData.size + 4} ${qrData.size + 4}`}\n                      className=\"size-full\"\n                      shapeRendering=\"crispEdges\"\n                    >\n                      <rect width=\"100%\" height=\"100%\" fill=\"transparent\" />\n                      <g transform=\"translate(2, 2)\">\n                        <path d={qrPath} fill=\"currentColor\" />\n                      </g>\n                    </svg>\n                  </div>\n\n                  <p className=\"text-muted-foreground text-xs\">\n                    Vector SVG scales to any resolution for billboard, print collateral, or event badges.\n                  </p>\n\n                  <Button\n                    aria-label=\"Download attachment\"\n                    variant=\"outline\"\n                    size=\"sm\"\n                    className=\"w-full gap-1.5 text-xs\"\n                    disabled={!generatedFullUrl}\n                    onClick={handleDownloadQr}\n                  >\n                    {downloadedQr ? (\n                      <Check className=\"text-success size-3.5\" aria-hidden=\"true\" />\n                    ) : (\n                      <Download className=\"size-3.5\" aria-hidden=\"true\" />\n                    )}\n                    {downloadedQr ? 'Downloaded SVG!' : 'Download QR Code (.SVG)'}\n                  </Button>\n                </div>\n              </div>\n\n              <Separator />\n\n              {/* Parameters Breakdown Table */}\n              <div className=\"space-y-2\">\n                <div className=\"flex items-center justify-between\">\n                  <span className=\"text-foreground text-xs font-semibold\">Parameters Verification</span>\n                  <span className=\"text-muted-foreground text-xs\">{activeQueryParameters.length}/5 Active</span>\n                </div>\n\n                <div className=\"border-border overflow-hidden rounded-md border text-xs\">\n                  <Table>\n                    <TableHeader>\n                      <TableRow className=\"hover:bg-transparent\">\n                        <TableHead className=\"h-7 text-xs font-medium\">Parameter</TableHead>\n                        <TableHead className=\"h-7 text-xs font-medium\">Value</TableHead>\n                        <TableHead className=\"h-7 text-right text-xs font-medium\">Status</TableHead>\n                      </TableRow>\n                    </TableHeader>\n                    <TableBody>\n                      <TableRow>\n                        <TableCell className=\"py-2 font-mono text-xs font-semibold\">utm_source</TableCell>\n                        <TableCell className=\"text-muted-foreground py-2 font-mono text-xs\">\n                          {utmSource.trim() || '—'}\n                        </TableCell>\n                        <TableCell className=\"py-2 text-right\">\n                          <Badge\n                            variant={utmSource.trim() ? 'default' : 'destructive'}\n                            className=\"px-1.5 py-0 text-xs font-normal\"\n                          >\n                            {utmSource.trim() ? 'Set' : 'Missing'}\n                          </Badge>\n                        </TableCell>\n                      </TableRow>\n\n                      <TableRow>\n                        <TableCell className=\"py-2 font-mono text-xs font-semibold\">utm_medium</TableCell>\n                        <TableCell className=\"text-muted-foreground py-2 font-mono text-xs\">\n                          {utmMedium.trim() || '—'}\n                        </TableCell>\n                        <TableCell className=\"py-2 text-right\">\n                          <Badge\n                            variant={utmMedium.trim() ? 'default' : 'destructive'}\n                            className=\"px-1.5 py-0 text-xs font-normal\"\n                          >\n                            {utmMedium.trim() ? 'Set' : 'Missing'}\n                          </Badge>\n                        </TableCell>\n                      </TableRow>\n\n                      <TableRow>\n                        <TableCell className=\"py-2 font-mono text-xs font-semibold\">utm_campaign</TableCell>\n                        <TableCell className=\"text-muted-foreground py-2 font-mono text-xs\">\n                          {utmCampaign.trim() || '—'}\n                        </TableCell>\n                        <TableCell className=\"py-2 text-right\">\n                          <Badge\n                            variant={utmCampaign.trim() ? 'default' : 'destructive'}\n                            className=\"px-1.5 py-0 text-xs font-normal\"\n                          >\n                            {utmCampaign.trim() ? 'Set' : 'Missing'}\n                          </Badge>\n                        </TableCell>\n                      </TableRow>\n\n                      <TableRow>\n                        <TableCell className=\"py-2 font-mono text-xs font-semibold\">utm_term</TableCell>\n                        <TableCell className=\"text-muted-foreground py-2 font-mono text-xs\">\n                          {utmTerm.trim() || '—'}\n                        </TableCell>\n                        <TableCell className=\"py-2 text-right\">\n                          <Badge variant=\"outline\" className=\"px-1.5 py-0 text-xs font-normal\">\n                            {utmTerm.trim() ? 'Set' : 'Optional'}\n                          </Badge>\n                        </TableCell>\n                      </TableRow>\n\n                      <TableRow>\n                        <TableCell className=\"py-2 font-mono text-xs font-semibold\">utm_content</TableCell>\n                        <TableCell className=\"text-muted-foreground py-2 font-mono text-xs\">\n                          {utmContent.trim() || '—'}\n                        </TableCell>\n                        <TableCell className=\"py-2 text-right\">\n                          <Badge variant=\"outline\" className=\"px-1.5 py-0 text-xs font-normal\">\n                            {utmContent.trim() ? 'Set' : 'Optional'}\n                          </Badge>\n                        </TableCell>\n                      </TableRow>\n                    </TableBody>\n                  </Table>\n                </div>\n              </div>\n            </CardContent>\n          </Card>\n        </div>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/UtmCampaignBuilder.tsx"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/badge.json",
    "https://uipkge.dev/r/react/button.json",
    "https://uipkge.dev/r/react/card.json",
    "https://uipkge.dev/r/react/input.json",
    "https://uipkge.dev/r/react/select.json",
    "https://uipkge.dev/r/react/separator.json",
    "https://uipkge.dev/r/react/table.json"
  ],
  "description": "Google Analytics 4 (GA4) campaign link generator with preset presets, shortlink generator, syntax-highlighted URL preview, parameter verification table, and vector QR code export.",
  "categories": [
    "marketing",
    "app",
    "form"
  ]
}