{
  "name": "cookie-consent",
  "type": "registry:block",
  "registryDependencies": [
    "button",
    "dialog",
    "switch",
    "card",
    "label",
    "accordion"
  ],
  "files": [
    {
      "path": "components/cookie-consent/cookie-provider.tsx",
      "type": "registry:file",
      "target": "components/cookie-consent/cookie-provider.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { GoogleConsentMode } from \"./google-consent-mode\";\nimport {\n  hasGoogleScripts as checkHasGoogleScripts,\n  getLoadedScripts,\n  loadConsentedScripts,\n  registerScript as registerScriptInternal,\n  unloadRevokedScripts,\n  unregisterScript as unregisterScriptInternal,\n} from \"./script-manager\";\nimport { retryFailedRecords, trackConsent } from \"./tracker\";\nimport type {\n  CategoryConfig,\n  ConsentCategories,\n  ConsentCategory,\n  ConsentChangeEvent,\n  ConsentState,\n  CookieConsentConfig,\n  CookieConsentContextValue,\n  GoogleConsentModeConfig,\n  ScriptConfig,\n} from \"./types\";\nimport {\n  calculateExpirationDate,\n  clearConsentState,\n  getAllAcceptedCategories,\n  getDefaultCategories,\n  getVisitorId,\n  isGoogleScript,\n  loadConsentState,\n  saveConsentState,\n} from \"./utils\";\n\nconst CookieConsentContext =\n  React.createContext<CookieConsentContextValue | null>(null);\n\nexport const defaultCategories: CategoryConfig[] = [\n  {\n    key: \"necessary\",\n    title: \"Necessary\",\n    description:\n      \"Essential cookies required for the website to function properly. These cannot be disabled.\",\n    required: true,\n  },\n  {\n    key: \"analytics\",\n    title: \"Analytics\",\n    description:\n      \"Cookies that help us understand how visitors interact with our website.\",\n  },\n  {\n    key: \"marketing\",\n    title: \"Marketing\",\n    description: \"Cookies used for advertising and tracking across websites.\",\n  },\n  {\n    key: \"preferences\",\n    title: \"Preferences\",\n    description: \"Cookies that remember your settings and preferences.\",\n  },\n];\n\ninterface CookieConsentProviderProps {\n  children: React.ReactNode;\n  config: CookieConsentConfig;\n}\n\nexport function CookieConsentProvider({\n  children,\n  config,\n}: CookieConsentProviderProps) {\n  const [state, setState] = React.useState<ConsentState>(() => ({\n    hasConsented: false,\n    categories: getDefaultCategories(),\n    lastUpdated: null,\n    consentVersion: config.consentVersion,\n    visitorId: \"\",\n  }));\n  const [isBannerVisible, setIsBannerVisible] = React.useState(false);\n  const [isSettingsOpen, setIsSettingsOpen] = React.useState(false);\n  const [isInitialized, setIsInitialized] = React.useState(false);\n  const [hasGoogleScripts, setHasGoogleScripts] = React.useState(false);\n\n  const previousCategoriesRef = React.useRef<ConsentCategories>(\n    getDefaultCategories()\n  );\n\n  // Auto-enable Google Consent Mode if Google scripts are detected\n  const effectiveGoogleConsentMode = React.useMemo(():\n    | GoogleConsentModeConfig\n    | undefined => {\n    // If user explicitly configured it, use their config\n    if (config.googleConsentMode) {\n      return config.googleConsentMode;\n    }\n    // If Google scripts are detected, auto-enable with defaults\n    if (hasGoogleScripts) {\n      return {\n        enabled: true,\n        mapping: {\n          analytics_storage: \"analytics\",\n          ad_storage: \"marketing\",\n          ad_user_data: \"marketing\",\n          ad_personalization: \"marketing\",\n          functionality_storage: \"preferences\",\n          personalization_storage: \"preferences\",\n          security_storage: \"necessary\",\n        },\n      };\n    }\n    return undefined;\n  }, [config.googleConsentMode, hasGoogleScripts]);\n\n  // Update Google Consent Mode v2 when consent changes\n  // Only updates if Google Consent Mode is enabled AND gtag exists (user is using Google services)\n  const updateGoogleConsentMode = React.useCallback(\n    (categories: ConsentCategories) => {\n      if (!effectiveGoogleConsentMode?.enabled) return;\n      if (typeof window === \"undefined\" || !window.gtag) return;\n      // Additional check: only update if dataLayer exists (Google services are actually loaded)\n      if (!window.dataLayer) return;\n\n      const mapping = effectiveGoogleConsentMode.mapping || {\n        analytics_storage: \"analytics\",\n        ad_storage: \"marketing\",\n        ad_user_data: \"marketing\",\n        ad_personalization: \"marketing\",\n        functionality_storage: \"preferences\",\n        personalization_storage: \"preferences\",\n        security_storage: \"necessary\",\n      };\n\n      const consentUpdate: Record<string, \"granted\" | \"denied\"> = {};\n\n      // Map consent categories to Google consent types\n      Object.entries(mapping).forEach(([googleType, category]) => {\n        if (category && categories[category]) {\n          consentUpdate[googleType] = \"granted\";\n        } else {\n          consentUpdate[googleType] = \"denied\";\n        }\n      });\n\n      // Update Google Consent Mode\n      window.gtag(\"consent\", \"update\", consentUpdate);\n    },\n    [effectiveGoogleConsentMode]\n  );\n\n  // Initialize state from localStorage\n  React.useEffect(() => {\n    const visitorId = getVisitorId();\n    const stored = loadConsentState();\n\n    if (stored && stored.consentVersion === config.consentVersion) {\n      setState({ ...stored, visitorId });\n      setIsBannerVisible(false);\n      previousCategoriesRef.current = stored.categories;\n\n      loadConsentedScripts(stored.categories);\n    } else {\n      setState((prev) => ({ ...prev, visitorId }));\n      setIsBannerVisible(true);\n    }\n\n    setIsInitialized(true);\n\n    if (config.traceability?.enabled) {\n      retryFailedRecords(config.traceability);\n    }\n  }, [config.consentVersion, config.traceability]);\n\n  // Check for existing Google scripts on mount\n  React.useEffect(() => {\n    // Check if any already registered scripts are Google scripts\n    if (checkHasGoogleScripts()) {\n      setHasGoogleScripts(true);\n    }\n  }, []);\n\n  const saveAndTrack = React.useCallback(\n    async (\n      categories: ConsentCategories,\n      action: \"accept_all\" | \"reject_all\" | \"custom\" | \"update\"\n    ) => {\n      const expirationDays = config.expirationDays ?? 365;\n      const expiresAt = calculateExpirationDate(expirationDays);\n      const visitorId = getVisitorId();\n\n      const previousCategories = previousCategoriesRef.current;\n\n      const newState: ConsentState = {\n        hasConsented: true,\n        categories,\n        lastUpdated: new Date().toISOString(),\n        consentVersion: config.consentVersion,\n        visitorId,\n      };\n\n      setState(newState);\n      saveConsentState(newState);\n      setIsBannerVisible(false);\n\n      const revokedCategories = unloadRevokedScripts(\n        previousCategories,\n        categories\n      );\n      loadConsentedScripts(categories);\n\n      // Update Google Consent Mode v2\n      updateGoogleConsentMode(categories);\n\n      const grantedCategories: ConsentCategory[] = [];\n      (Object.keys(categories) as ConsentCategory[]).forEach((category) => {\n        if (!previousCategories[category] && categories[category]) {\n          grantedCategories.push(category);\n        }\n      });\n\n      if (config.onConsentChange) {\n        const event: ConsentChangeEvent = {\n          previousCategories,\n          currentCategories: categories,\n          action,\n          revokedCategories,\n          grantedCategories,\n        };\n        config.onConsentChange(event);\n      }\n\n      // Update previous categories ref\n      previousCategoriesRef.current = categories;\n\n      // Track consent if traceability is enabled\n      if (config.traceability?.enabled) {\n        const userId = await config.consentScope?.getUserId?.();\n        await trackConsent({\n          categories,\n          action,\n          consentVersion: config.consentVersion,\n          expiresAt,\n          config: config.traceability,\n          userId: userId ?? undefined,\n          scope: config.consentScope?.mode === \"global\" ? \"global\" : \"device\",\n        });\n      }\n    },\n    [config, updateGoogleConsentMode]\n  );\n\n  const acceptAll = React.useCallback(async () => {\n    await saveAndTrack(getAllAcceptedCategories(), \"accept_all\");\n  }, [saveAndTrack]);\n\n  const rejectAll = React.useCallback(async () => {\n    await saveAndTrack(getDefaultCategories(), \"reject_all\");\n  }, [saveAndTrack]);\n\n  const updateConsent = React.useCallback(\n    async (categories: Partial<ConsentCategories>) => {\n      const newCategories: ConsentCategories = {\n        ...state.categories,\n        ...categories,\n        necessary: true,\n      };\n      const action = state.hasConsented ? \"update\" : \"custom\";\n      await saveAndTrack(newCategories, action);\n    },\n    [state.categories, state.hasConsented, saveAndTrack]\n  );\n\n  const openSettings = React.useCallback(() => {\n    setIsSettingsOpen(true);\n  }, []);\n\n  const closeSettings = React.useCallback(() => {\n    setIsSettingsOpen(false);\n  }, []);\n\n  const hideBanner = React.useCallback(() => {\n    setIsBannerVisible(false);\n  }, []);\n\n  const resetConsent = React.useCallback(() => {\n    const defaultCats = getDefaultCategories();\n    unloadRevokedScripts(state.categories, defaultCats);\n\n    clearConsentState();\n    setState({\n      hasConsented: false,\n      categories: defaultCats,\n      lastUpdated: null,\n      consentVersion: config.consentVersion,\n      visitorId: getVisitorId(),\n    });\n    previousCategoriesRef.current = defaultCats;\n    setIsBannerVisible(true);\n  }, [config.consentVersion, state.categories]);\n\n  const hasConsent = React.useCallback(\n    (category: \"necessary\" | \"analytics\" | \"marketing\" | \"preferences\") => {\n      return state.categories[category] ?? false;\n    },\n    [state.categories]\n  );\n\n  const registerScript = React.useCallback((script: ScriptConfig) => {\n    registerScriptInternal(script);\n\n    // Auto-detect Google scripts and enable Google Consent Mode\n    if (isGoogleScript(script)) {\n      setHasGoogleScripts(true);\n    }\n  }, []);\n\n  const unregisterScript = React.useCallback((id: string) => {\n    unregisterScriptInternal(id);\n  }, []);\n\n  const value: CookieConsentContextValue = React.useMemo(\n    () => ({\n      state,\n      isBannerVisible: isInitialized && isBannerVisible,\n      isSettingsOpen,\n      acceptAll,\n      rejectAll,\n      updateConsent,\n      openSettings,\n      closeSettings,\n      hideBanner,\n      resetConsent,\n      hasConsent,\n      config,\n      registerScript,\n      unregisterScript,\n      getLoadedScripts,\n    }),\n    [\n      state,\n      isInitialized,\n      isBannerVisible,\n      isSettingsOpen,\n      acceptAll,\n      rejectAll,\n      updateConsent,\n      openSettings,\n      closeSettings,\n      hideBanner,\n      resetConsent,\n      hasConsent,\n      config,\n      registerScript,\n      unregisterScript,\n    ]\n  );\n\n  return (\n    <CookieConsentContext.Provider value={value}>\n      {children}\n      {/* Automatically render GoogleConsentMode when Google scripts are detected */}\n      {hasGoogleScripts && effectiveGoogleConsentMode?.enabled && (\n        <GoogleConsentMode\n          defaults={{\n            analytics_storage: \"denied\",\n            ad_storage: \"denied\",\n            ad_user_data: \"denied\",\n            ad_personalization: \"denied\",\n          }}\n          regions={effectiveGoogleConsentMode?.regions}\n        />\n      )}\n    </CookieConsentContext.Provider>\n  );\n}\n\nexport function useCookieConsent(): CookieConsentContextValue {\n  const context = React.useContext(CookieConsentContext);\n  if (!context) {\n    throw new Error(\n      \"useCookieConsent must be used within a CookieConsentProvider\"\n    );\n  }\n  return context;\n}\n\nexport { CookieConsentContext };\n"
    },
    {
      "path": "components/cookie-consent/cookie-banner.tsx",
      "type": "registry:file",
      "target": "components/cookie-consent/cookie-banner.tsx",
      "content": "\"use client\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { Cookie, Settings } from \"lucide-react\";\nimport { useCookieConsent } from \"./cookie-provider\";\n\nexport interface CookieBannerProps {\n  className?: string;\n}\n\nexport function CookieBanner({ className }: CookieBannerProps) {\n  const { isBannerVisible, acceptAll, rejectAll, openSettings, config } =\n    useCookieConsent();\n\n  const positionClasses = {\n    bottom: \"inset-x-0 bottom-0\",\n    top: \"inset-x-0 top-0\",\n    \"bottom-left\": \"bottom-4 left-4 max-w-md\",\n    \"bottom-right\": \"bottom-4 right-4 max-w-md\",\n  };\n\n  const position = config.position ?? \"bottom\";\n\n  return (\n    <AnimatePresence>\n      {isBannerVisible && (\n        <motion.div\n          initial={{ y: position.includes(\"top\") ? -100 : 100, opacity: 0 }}\n          animate={{ y: 0, opacity: 1 }}\n          exit={{ y: position.includes(\"top\") ? -100 : 100, opacity: 0 }}\n          transition={{ type: \"spring\", damping: 25, stiffness: 300 }}\n          className={cn(\"fixed z-50 p-4\", positionClasses[position], className)}\n        >\n          <div\n            className={cn(\n              \"bg-card border border-border rounded-lg shadow-lg\",\n              position === \"bottom\" || position === \"top\"\n                ? \"mx-auto max-w-5xl\"\n                : \"\"\n            )}\n          >\n            <div className=\"p-6\">\n              <div className=\"flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n                <div className=\"flex items-start gap-4\">\n                  <div className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-muted\">\n                    <Cookie className=\"h-5 w-5 text-muted-foreground\" />\n                  </div>\n                  <div className=\"space-y-1\">\n                    <h3 className=\"text-sm font-semibold text-foreground\">\n                      Cookie Preferences\n                    </h3>\n                    <p className=\"text-sm text-muted-foreground leading-relaxed max-w-xl\">\n                      We use cookies to enhance your experience. By continuing\n                      to visit this site you agree to our use of cookies.{\" \"}\n                      {config.privacyPolicyUrl && (\n                        <a\n                          href={config.privacyPolicyUrl}\n                          className=\"underline underline-offset-4 hover:text-foreground transition-colors\"\n                          target=\"_blank\"\n                          rel=\"noopener noreferrer\"\n                        >\n                          Learn more\n                        </a>\n                      )}\n                    </p>\n                  </div>\n                </div>\n\n                <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n                  <Button\n                    variant=\"outline\"\n                    size=\"sm\"\n                    onClick={openSettings}\n                    className=\"gap-2 bg-transparent\"\n                  >\n                    <Settings className=\"h-4 w-4\" />\n                    Customize\n                  </Button>\n                  <Button size=\"sm\" onClick={rejectAll}>\n                    Reject All\n                  </Button>\n                  <Button size=\"sm\" onClick={acceptAll}>\n                    Accept All\n                  </Button>\n                </div>\n              </div>\n            </div>\n          </div>\n        </motion.div>\n      )}\n    </AnimatePresence>\n  );\n}\n"
    },
    {
      "path": "components/cookie-consent/cookie-settings.tsx",
      "type": "registry:file",
      "target": "components/cookie-consent/cookie-settings.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Check, Shield } from \"lucide-react\"\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n} from \"@/components/ui/dialog\"\nimport { Button } from \"@/components/ui/button\"\nimport { Switch } from \"@/components/ui/switch\"\nimport { Label } from \"@/components/ui/label\"\nimport { Separator } from \"@/components/ui/separator\"\nimport { useCookieConsent, defaultCategories } from \"./cookie-provider\"\nimport type { ConsentCategories, ConsentCategory } from \"./types\"\nimport { getDefaultCategories, getAllAcceptedCategories } from \"./utils\"\nimport { cn } from \"@/lib/utils\"\n\nexport interface CookieSettingsProps {\n  className?: string\n}\n\nexport function CookieSettings({ className }: CookieSettingsProps) {\n  const { isSettingsOpen, closeSettings, state, updateConsent, config, acceptAll, rejectAll } = useCookieConsent()\n\n  const categories = config.categories ?? defaultCategories\n\n  const [localCategories, setLocalCategories] = React.useState<ConsentCategories>(state.categories)\n\n  // Sync local state when modal opens or when state changes\n  React.useEffect(() => {\n    if (isSettingsOpen) {\n      setLocalCategories(state.categories)\n    }\n  }, [isSettingsOpen, state.categories])\n\n  const handleToggle = (key: ConsentCategory, checked: boolean) => {\n    setLocalCategories((prev) => ({\n      ...prev,\n      [key]: checked,\n    }))\n  }\n\n  const handleSave = async () => {\n    await updateConsent(localCategories)\n    closeSettings()\n  }\n\n  const handleAcceptAll = async () => {\n    const allAccepted = getAllAcceptedCategories()\n    // Update local state immediately for UI feedback\n    setLocalCategories(allAccepted)\n    await acceptAll()\n    closeSettings()\n  }\n\n  const handleRejectAll = async () => {\n    const defaultCats = getDefaultCategories()\n    // Update local state immediately for UI feedback\n    setLocalCategories(defaultCats)\n    await rejectAll()\n    closeSettings()\n  }\n\n  return (\n    <Dialog open={isSettingsOpen} onOpenChange={(open) => !open && closeSettings()}>\n      <DialogContent className={cn(\"sm:max-w-lg\", className)}>\n        <DialogHeader>\n          <div className=\"flex items-center gap-3\">\n            <div className=\"flex h-10 w-10 items-center justify-center rounded-full bg-muted\">\n              <Shield className=\"h-5 w-5 text-muted-foreground\" />\n            </div>\n            <div>\n              <DialogTitle>Cookie Settings</DialogTitle>\n              <DialogDescription>Manage your cookie preferences below.</DialogDescription>\n            </div>\n          </div>\n        </DialogHeader>\n\n        <Separator />\n\n        <div className=\"space-y-4 py-4 max-h-[60vh] overflow-y-auto\">\n          {categories.map((category) => {\n            const isEnabled = localCategories[category.key]\n            const isRequired = category.required\n\n            return (\n              <div\n                key={category.key}\n                className={cn(\n                  \"flex items-start justify-between gap-4 rounded-lg border p-4 transition-colors\",\n                  isEnabled ? \"border-primary/20 bg-primary/5\" : \"border-border\",\n                )}\n              >\n                <div className=\"space-y-1\">\n                  <div className=\"flex items-center gap-2\">\n                    <Label htmlFor={`cookie-${category.key}`} className=\"text-sm font-medium cursor-pointer\">\n                      {category.title}\n                    </Label>\n                    {isRequired && (\n                      <span className=\"text-xs text-muted-foreground bg-muted px-2 py-0.5 rounded\">Required</span>\n                    )}\n                  </div>\n                  <p className=\"text-sm text-muted-foreground leading-relaxed\">{category.description}</p>\n                </div>\n                <Switch\n                  id={`cookie-${category.key}`}\n                  checked={isEnabled}\n                  onCheckedChange={(checked) => handleToggle(category.key, checked)}\n                  disabled={isRequired}\n                  aria-label={`Toggle ${category.title} cookies`}\n                />\n              </div>\n            )\n          })}\n        </div>\n\n        <Separator />\n\n        <DialogFooter className=\"flex-col gap-2 sm:flex-row\">\n          <Button variant=\"outline\" size=\"sm\" onClick={handleRejectAll} className=\"w-full sm:w-auto bg-transparent\">\n            Reject All\n          </Button>\n          <Button variant=\"outline\" size=\"sm\" onClick={handleAcceptAll} className=\"w-full sm:w-auto bg-transparent\">\n            Accept All\n          </Button>\n          <Button size=\"sm\" onClick={handleSave} className=\"w-full sm:w-auto gap-2\">\n            <Check className=\"h-4 w-4\" />\n            Save Preferences\n          </Button>\n        </DialogFooter>\n\n        {config.privacyPolicyUrl && (\n          <p className=\"text-xs text-center text-muted-foreground\">\n            Read our{\" \"}\n            <a\n              href={config.privacyPolicyUrl}\n              className=\"underline underline-offset-4 hover:text-foreground transition-colors\"\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n            >\n              Privacy Policy\n            </a>\n          </p>\n        )}\n      </DialogContent>\n    </Dialog>\n  )\n}\n"
    },
    {
      "path": "components/cookie-consent/cookie-trigger.tsx",
      "type": "registry:file",
      "target": "components/cookie-consent/cookie-trigger.tsx",
      "content": "\"use client\"\nimport { Cookie } from \"lucide-react\"\nimport { Button } from \"@/components/ui/button\"\nimport { useCookieConsent } from \"./cookie-provider\"\nimport { cn } from \"@/lib/utils\"\n\nexport interface CookieTriggerProps {\n  className?: string\n  variant?: \"icon\" | \"text\" | \"full\"\n}\n\n/**\n * A trigger button to reopen cookie settings after initial consent\n */\nexport function CookieTrigger({ className, variant = \"text\" }: CookieTriggerProps) {\n  const { openSettings, state } = useCookieConsent()\n\n  if (!state.hasConsented) {\n    return null\n  }\n\n  if (variant === \"icon\") {\n    return (\n      <Button\n        variant=\"ghost\"\n        size=\"icon\"\n        onClick={openSettings}\n        className={cn(\"h-8 w-8\", className)}\n        aria-label=\"Cookie settings\"\n      >\n        <Cookie className=\"h-4 w-4\" />\n      </Button>\n    )\n  }\n\n  if (variant === \"full\") {\n    return (\n      <Button variant=\"outline\" size=\"sm\" onClick={openSettings} className={cn(\"gap-2\", className)}>\n        <Cookie className=\"h-4 w-4\" />\n        Cookie Settings\n      </Button>\n    )\n  }\n\n  return (\n    <button\n      onClick={openSettings}\n      className={cn(\n        \"text-sm text-muted-foreground hover:text-foreground underline underline-offset-4 transition-colors\",\n        className,\n      )}\n    >\n      Cookie Settings\n    </button>\n  )\n}\n"
    },
    {
      "path": "components/cookie-consent/consent-script.tsx",
      "type": "registry:file",
      "target": "components/cookie-consent/consent-script.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { useCookieConsent } from \"./cookie-provider\"\nimport type { ConsentCategory, ScriptConfig } from \"./types\"\nimport { loadScript, registerCleanup, registerScript, unregisterScript } from \"./script-manager\"\n\nexport interface ConsentScriptProps {\n  /** Unique identifier for the script */\n  id: string\n  /** External script URL */\n  src?: string\n  /** Inline script content */\n  children?: string\n  /** Consent category required to load */\n  category: ConsentCategory\n  /** Script loading strategy */\n  strategy?: \"afterInteractive\" | \"lazyOnload\" | \"beforeInteractive\"\n  /** Additional script attributes */\n  attributes?: Record<string, string>\n  /** Callback when script loads */\n  onLoad?: () => void\n  /** Callback when script fails to load */\n  onError?: (error: Error) => void\n  /** Callback when consent is revoked - use for cleanup */\n  onRevoke?: () => void\n}\n\n/**\n * ConsentScript - A component that conditionally loads scripts based on user consent.\n *\n * Similar to Next.js Script component but with consent awareness.\n *\n * @example\n * // External script\n * <ConsentScript\n *   id=\"google-analytics\"\n *   src=\"https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID\"\n *   category=\"analytics\"\n *   onRevoke={() => {\n *     // Cleanup GA cookies and global\n *     window.ga = undefined\n *   }}\n * />\n *\n * @example\n * // Inline script\n * <ConsentScript id=\"analytics-init\" category=\"analytics\">\n *   {`window.dataLayer = window.dataLayer || [];`}\n * </ConsentScript>\n */\nexport function ConsentScript({\n  id,\n  src,\n  children,\n  category,\n  strategy = \"afterInteractive\",\n  attributes,\n  onLoad,\n  onError,\n  onRevoke,\n}: ConsentScriptProps) {\n  const { hasConsent, registerScript: ctxRegister } = useCookieConsent()\n  const hasConsentForCategory = hasConsent(category)\n  const [isLoaded, setIsLoaded] = React.useState(false)\n  const [error, setError] = React.useState<Error | null>(null)\n\n  // Register script on mount\n  React.useEffect(() => {\n    const config: ScriptConfig = {\n      id,\n      src,\n      content: children,\n      category,\n      strategy,\n      attributes,\n      onLoad: () => {\n        setIsLoaded(true)\n        onLoad?.()\n      },\n      onError: (err) => {\n        setError(err)\n        onError?.(err)\n      },\n      onRevoke: () => {\n        setIsLoaded(false)\n        onRevoke?.()\n      },\n    }\n\n    registerScript(config)\n    ctxRegister(config)\n\n    return () => {\n      unregisterScript(id)\n    }\n  }, [id, src, children, category, strategy, attributes, onLoad, onError, onRevoke, ctxRegister])\n\n  // Load script when consent is granted\n  React.useEffect(() => {\n    if (hasConsentForCategory && !isLoaded && !error) {\n      loadScript(id).catch((err) => {\n        setError(err)\n        onError?.(err)\n      })\n    }\n  }, [hasConsentForCategory, isLoaded, error, id, onError])\n\n  // Register cleanup if provided\n  React.useEffect(() => {\n    if (onRevoke) {\n      registerCleanup(id, onRevoke)\n    }\n  }, [id, onRevoke])\n\n  // This component doesn't render anything visible\n  return null\n}\n\nConsentScript.displayName = \"ConsentScript\"\n"
    },
    {
      "path": "components/cookie-consent/index.ts",
      "type": "registry:file",
      "target": "components/cookie-consent/index.ts",
      "content": "// Main exports for cookie consent components\nexport { ConsentScript } from \"./consent-script\";\nexport { CookieBanner } from \"./cookie-banner\";\nexport { CookieBannerBackdrop } from \"./cookie-banner-backdrop\";\nexport {\n  CookieConsentProvider,\n  defaultCategories,\n  useCookieConsent,\n} from \"./cookie-provider\";\nexport { CookieSettings } from \"./cookie-settings\";\nexport { CookieTrigger } from \"./cookie-trigger\";\nexport { GoogleConsentMode } from \"./google-consent-mode\";\nexport { useConsentScript } from \"./use-consent-script\";\nexport { useConsentGate, useConsentValue } from \"./use-cookie-consent\";\n\n// Types\nexport type {\n  BannerPosition,\n  CategoryConfig,\n  ConsentAction,\n  ConsentCategories,\n  ConsentCategory,\n  ConsentChangeEvent,\n  ConsentRecord,\n  ConsentScope,\n  ConsentScopeConfig,\n  ConsentState,\n  CookieConsentConfig,\n  CookieConsentContextValue,\n  GoogleConsentModeConfig,\n  ScriptConfig,\n  TraceabilityConfig,\n} from \"./types\";\n\n// Utilities\nexport {\n  getLoadedScripts,\n  hasGoogleScripts,\n  loadScript,\n  registerCleanup,\n  registerScript,\n  scriptCleanupHelpers,\n  unloadScript,\n  unregisterScript,\n} from \"./script-manager\";\nexport { retryFailedRecords, trackConsent } from \"./tracker\";\nexport { generateUUID, getVisitorId, isGoogleScript } from \"./utils\";\n\n// Note: Test utilities are not exported from the main index to avoid\n// bundling test dependencies in production builds. Import them directly\n// from \"./test-utils\" in test files only.\n"
    },
    {
      "path": "components/cookie-consent/types.ts",
      "type": "registry:file",
      "target": "components/cookie-consent/types.ts",
      "content": "// Cookie consent types following shadcn patterns\n\nexport type ConsentCategory = \"necessary\" | \"analytics\" | \"marketing\" | \"preferences\"\n\nexport type ConsentAction = \"accept_all\" | \"reject_all\" | \"custom\" | \"update\"\n\nexport type ConsentScope = \"device\" | \"global\"\n\nexport type BannerPosition = \"bottom\" | \"top\" | \"bottom-left\" | \"bottom-right\"\n\nexport interface ConsentCategories {\n  necessary: boolean\n  analytics: boolean\n  marketing: boolean\n  preferences: boolean\n}\n\nexport interface ConsentRecord {\n  /** Unique device identifier */\n  visitorId: string\n  /** Unique consent action identifier */\n  consentId: string\n  /** Version of consent policy */\n  consentVersion: string\n  /** User account ID when authenticated */\n  userId?: string\n  /** Consent scope */\n  scope: ConsentScope\n  /** Category consent values */\n  categories: ConsentCategories\n  /** Type of action taken */\n  action: ConsentAction\n  /** ISO 8601 timestamp */\n  timestamp: string\n  /** When consent expires */\n  expiresAt: string\n  /** Page URL where consent was given */\n  url: string\n  /** Browser user agent */\n  userAgent: string\n  /** Browser language */\n  language: string\n  /** Device ID that was linked (for global scope) */\n  linkedFromDevice?: string\n}\n\nexport interface TraceabilityConfig {\n  /** Enable consent traceability */\n  enabled: boolean\n  /** API endpoint to send consent records */\n  endpoint: string\n  /** HTTP method */\n  method?: \"POST\" | \"PUT\"\n  /** Custom headers */\n  headers?: Record<string, string>\n  /** Custom visitor ID generator */\n  getVisitorId?: () => string | Promise<string>\n  /** Include user agent in record */\n  includeUserAgent?: boolean\n  /** Include URL in record */\n  includeUrl?: boolean\n  /** Retry on failure */\n  retryOnFailure?: boolean\n  /** Max retry attempts */\n  maxRetries?: number\n  /** Success callback */\n  onSuccess?: (record: ConsentRecord) => void\n  /** Error callback */\n  onError?: (error: Error, record: ConsentRecord) => void\n}\n\nexport interface ConsentScopeConfig {\n  /** Consent scope mode */\n  mode: \"device\" | \"global\" | \"hybrid\"\n  /** Sync endpoint for global/hybrid mode */\n  syncEndpoint?: string\n  /** Get user ID for global consent */\n  getUserId?: () => string | null | Promise<string | null>\n  /** Conflict resolution strategy */\n  conflictStrategy?: \"server-wins\" | \"device-wins\" | \"most-recent\"\n  /** Sync on authentication */\n  syncOnAuth?: boolean\n  /** Link device to user */\n  linkDeviceToUser?: boolean\n}\n\nexport interface CategoryConfig {\n  key: ConsentCategory\n  title: string\n  description: string\n  required?: boolean\n}\n\nexport interface ScriptConfig {\n  /** Unique identifier for the script */\n  id: string\n  /** Script source URL */\n  src?: string\n  /** Inline script content */\n  content?: string\n  /** Consent category required */\n  category: ConsentCategory\n  /** Script loading strategy */\n  strategy?: \"afterInteractive\" | \"lazyOnload\" | \"beforeInteractive\"\n  /** Additional script attributes */\n  attributes?: Record<string, string>\n  /** Callback when script loads */\n  onLoad?: () => void\n  /** Callback when script fails */\n  onError?: (error: Error) => void\n  /** Cleanup function when consent is revoked */\n  onRevoke?: () => void\n}\n\nexport interface ConsentChangeEvent {\n  previousCategories: ConsentCategories\n  currentCategories: ConsentCategories\n  action: ConsentAction\n  revokedCategories: ConsentCategory[]\n  grantedCategories: ConsentCategory[]\n}\n\nexport interface GoogleConsentModeConfig {\n  /** Enable Google Consent Mode v2 integration */\n  enabled: boolean\n  /** Custom mapping of consent categories to Google consent types */\n  mapping?: {\n    analytics_storage?: ConsentCategory\n    ad_storage?: ConsentCategory\n    ad_user_data?: ConsentCategory\n    ad_personalization?: ConsentCategory\n    functionality_storage?: ConsentCategory\n    personalization_storage?: ConsentCategory\n    security_storage?: ConsentCategory\n  }\n  /** Regions to apply consent mode to (empty = all regions) */\n  regions?: string[]\n}\n\nexport interface CookieConsentConfig {\n  consentVersion: string\n  expirationDays?: number\n  privacyPolicyUrl?: string\n  position?: BannerPosition\n  categories?: CategoryConfig[]\n  traceability?: TraceabilityConfig\n  consentScope?: ConsentScopeConfig\n  googleConsentMode?: GoogleConsentModeConfig\n  onConsentChange?: (event: ConsentChangeEvent) => void\n}\n\nexport interface ConsentState {\n  /** Whether consent has been given */\n  hasConsented: boolean\n  /** Current consent categories */\n  categories: ConsentCategories\n  /** When consent was last updated */\n  lastUpdated: string | null\n  /** Consent version */\n  consentVersion: string\n  /** Visitor ID */\n  visitorId: string\n}\n\nexport interface CookieConsentContextValue {\n  state: ConsentState\n  isBannerVisible: boolean\n  isSettingsOpen: boolean\n  acceptAll: () => Promise<void>\n  rejectAll: () => Promise<void>\n  updateConsent: (categories: Partial<ConsentCategories>) => Promise<void>\n  openSettings: () => void\n  closeSettings: () => void\n  hideBanner: () => void\n  resetConsent: () => void\n  hasConsent: (category: ConsentCategory) => boolean\n  config: CookieConsentConfig\n  registerScript: (script: ScriptConfig) => void\n  unregisterScript: (id: string) => void\n  getLoadedScripts: () => string[]\n}\n"
    },
    {
      "path": "components/cookie-consent/utils.ts",
      "type": "registry:file",
      "target": "components/cookie-consent/utils.ts",
      "content": "import type { ConsentCategories, ConsentState } from \"./types\"\n\nconst STORAGE_KEY = \"cookie-consent\"\nconst VISITOR_ID_KEY = \"cookie-consent-visitor-id\"\n\n/**\n * Generate a UUID v4\n */\nexport function generateUUID(): string {\n  return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n    const r = (Math.random() * 16) | 0\n    const v = c === \"x\" ? r : (r & 0x3) | 0x8\n    return v.toString(16)\n  })\n}\n\n/**\n * Get or create a visitor ID\n */\nexport function getVisitorId(): string {\n  if (typeof window === \"undefined\") {\n    return generateUUID()\n  }\n\n  let visitorId = localStorage.getItem(VISITOR_ID_KEY)\n  if (!visitorId) {\n    visitorId = generateUUID()\n    localStorage.setItem(VISITOR_ID_KEY, visitorId)\n  }\n  return visitorId\n}\n\n/**\n * Get default consent categories (all false except necessary)\n */\nexport function getDefaultCategories(): ConsentCategories {\n  return {\n    necessary: true,\n    analytics: false,\n    marketing: false,\n    preferences: false,\n  }\n}\n\n/**\n * Get all categories accepted\n */\nexport function getAllAcceptedCategories(): ConsentCategories {\n  return {\n    necessary: true,\n    analytics: true,\n    marketing: true,\n    preferences: true,\n  }\n}\n\n/**\n * Save consent state to localStorage\n */\nexport function saveConsentState(state: ConsentState): void {\n  if (typeof window === \"undefined\") return\n  localStorage.setItem(STORAGE_KEY, JSON.stringify(state))\n}\n\n/**\n * Load consent state from localStorage\n */\nexport function loadConsentState(): ConsentState | null {\n  if (typeof window === \"undefined\") return null\n\n  const stored = localStorage.getItem(STORAGE_KEY)\n  if (!stored) return null\n\n  try {\n    return JSON.parse(stored) as ConsentState\n  } catch {\n    return null\n  }\n}\n\n/**\n * Clear consent state from localStorage\n */\nexport function clearConsentState(): void {\n  if (typeof window === \"undefined\") return\n  localStorage.removeItem(STORAGE_KEY)\n}\n\n/**\n * Calculate expiration date\n */\nexport function calculateExpirationDate(days: number): string {\n  const date = new Date()\n  date.setDate(date.getDate() + days)\n  return date.toISOString()\n}\n\n/**\n * Check if consent has expired\n */\nexport function isConsentExpired(expiresAt: string): boolean {\n  return new Date(expiresAt) < new Date()\n}\n\n/**\n * Check if a script is a Google service script\n * Detects Google Analytics, Google Tag Manager, Google Ads, etc.\n */\nexport function isGoogleScript(script: { src?: string; content?: string }): boolean {\n  // Check if src URL contains Google domains (case-insensitive)\n  if (script.src) {\n    const srcLower = script.src.toLowerCase()\n    const googleDomains = [\n      \"googletagmanager.com\",\n      \"google-analytics.com\",\n      \"googleadservices.com\",\n      \"google.com/analytics\",\n      \"google.com/ads\",\n      \"doubleclick.net\",\n      \"googleapis.com/gtag\",\n    ]\n    \n    // Check if any Google domain is present in the URL\n    // Use better matching to avoid false positives (e.g., \"fakegoogletagmanager.com\")\n    const isGoogleDomain = googleDomains.some((domain) => {\n      const domainLower = domain.toLowerCase()\n      // For full domains, check for domain boundaries (preceded by . or // or start of string)\n      if (domainLower.includes(\"/\")) {\n        // For paths like \"google.com/analytics\", just check if it's included\n        return srcLower.includes(domainLower)\n      } else {\n        // For domains, check for proper domain boundaries\n        // Match: .googletagmanager.com or //googletagmanager.com or googletagmanager.com/\n        const domainPattern = new RegExp(\n          `(^|//|\\\\.)${domainLower.replace(/\\./g, \"\\\\.\")}(/|:|$|\\\\?)`,\n          \"i\"\n        )\n        return domainPattern.test(srcLower)\n      }\n    })\n    \n    if (isGoogleDomain) {\n      return true\n    }\n  }\n\n  // Check if inline content contains Google-specific code (case-insensitive)\n  // This is checked as a fallback if src doesn't match, or if no src is provided\n  if (script.content) {\n    const contentLower = script.content.toLowerCase()\n    const googlePatterns = [\n      \"googletagmanager.com\",\n      \"google-analytics.com\",\n      \"gtag(\",\n      \"datalayer\",\n      \"ga(\",\n      \"google-analytics\",\n    ]\n    return googlePatterns.some((pattern) => contentLower.includes(pattern.toLowerCase()))\n  }\n\n  return false\n}\n"
    },
    {
      "path": "components/cookie-consent/use-cookie-consent.ts",
      "type": "registry:file",
      "target": "components/cookie-consent/use-cookie-consent.ts",
      "content": "\"use client\"\n\nimport { useContext } from \"react\"\nimport { CookieConsentContext } from \"./cookie-provider\"\nimport type { ConsentCategory, CookieConsentContextValue } from \"./types\"\n\n/**\n * Hook to access cookie consent context\n */\nexport function useCookieConsent(): CookieConsentContextValue {\n  const context = useContext(CookieConsentContext)\n  if (!context) {\n    throw new Error(\"useCookieConsent must be used within a CookieConsentProvider\")\n  }\n  return context\n}\n\n/**\n * Hook to check if a specific category is consented\n */\nexport function useConsentValue(category: ConsentCategory): boolean {\n  const { hasConsent } = useCookieConsent()\n  return hasConsent(category)\n}\n\n/**\n * Hook to conditionally render based on consent\n */\nexport function useConsentGate(category: ConsentCategory): {\n  isAllowed: boolean\n  isLoading: boolean\n} {\n  const { state } = useCookieConsent()\n\n  return {\n    isAllowed: state.categories[category] ?? false,\n    isLoading: !state.hasConsented,\n  }\n}\n"
    },
    {
      "path": "components/cookie-consent/use-consent-script.ts",
      "type": "registry:file",
      "target": "components/cookie-consent/use-consent-script.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { useCookieConsent } from \"./cookie-provider\";\nimport {\n  loadScript,\n  registerCleanup,\n  registerScript,\n  unloadScript,\n  unregisterScript,\n} from \"./script-manager\";\nimport type { ConsentCategory } from \"./types\";\n\ninterface UseConsentScriptOptions {\n  /** External script URL */\n  src?: string;\n  /** Inline script content */\n  content?: string;\n  /** Additional script attributes */\n  attributes?: Record<string, string>;\n  /** Script loading strategy */\n  strategy?: \"afterInteractive\" | \"lazyOnload\" | \"beforeInteractive\";\n  /** Cleanup function when consent is revoked */\n  onRevoke?: () => void;\n}\n\ninterface UseConsentScriptReturn {\n  /** Whether the script is loaded */\n  isLoaded: boolean;\n  /** Whether the script is currently loading */\n  isLoading: boolean;\n  /** Whether consent is granted for this category */\n  hasConsent: boolean;\n  /** Any error that occurred */\n  error: Error | null;\n  /** Manually trigger script load (if consent is granted) */\n  load: () => Promise<void>;\n  /** Manually unload the script */\n  unload: () => void;\n}\n\n/**\n * Hook for programmatic consent-aware script loading\n *\n * @example\n * const { isLoaded, hasConsent } = useConsentScript(\"analytics\", \"gtag\", {\n *   src: \"https://www.googletagmanager.com/gtag/js?id=GA_ID\",\n *   onRevoke: () => {\n *     window.gtag = undefined\n *   }\n * })\n *\n * useEffect(() => {\n *   if (isLoaded) {\n *     gtag('config', 'GA_ID')\n *   }\n * }, [isLoaded])\n */\nexport function useConsentScript(\n  category: ConsentCategory,\n  id: string,\n  options: UseConsentScriptOptions = {}\n): UseConsentScriptReturn {\n  const { hasConsent: checkConsent, registerScript: ctxRegister } =\n    useCookieConsent();\n  const consentGranted = checkConsent(category);\n\n  const [isLoaded, setIsLoaded] = React.useState(false);\n  const [isLoading, setIsLoading] = React.useState(false);\n  const [error, setError] = React.useState<Error | null>(null);\n\n  // Use refs to avoid re-registration on every render\n  const isRegisteredRef = React.useRef(false);\n  const onRevokeRef = React.useRef(options.onRevoke);\n\n  // Keep onRevoke ref up to date\n  React.useLayoutEffect(() => {\n    onRevokeRef.current = options.onRevoke;\n  }, [options.onRevoke]);\n\n  // Register script only once\n  React.useEffect(() => {\n    if (isRegisteredRef.current) return;\n    isRegisteredRef.current = true;\n\n    registerScript({\n      id,\n      src: options.src,\n      content: options.content,\n      category,\n      strategy: options.strategy,\n      attributes: options.attributes,\n      onLoad: () => setIsLoaded(true),\n      onError: setError,\n      onRevoke: () => {\n        setIsLoaded(false);\n        onRevokeRef.current?.();\n      },\n    });\n\n    ctxRegister({\n      id,\n      src: options.src,\n      content: options.content,\n      category,\n      strategy: options.strategy,\n      attributes: options.attributes,\n    });\n\n    if (onRevokeRef.current) {\n      registerCleanup(id, () => onRevokeRef.current?.());\n    }\n\n    return () => {\n      isRegisteredRef.current = false;\n      unregisterScript(id);\n    };\n    // Only re-register if id changes - other options are captured in initial registration\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [id]);\n\n  // Auto-load when consent is granted\n  React.useEffect(() => {\n    if (consentGranted && !isLoaded && !isLoading && !error) {\n      setIsLoading(true);\n      loadScript(id)\n        .then(() => {\n          setIsLoaded(true);\n          setIsLoading(false);\n        })\n        .catch((err) => {\n          setError(err);\n          setIsLoading(false);\n        });\n    }\n  }, [consentGranted, isLoaded, isLoading, error, id]);\n\n  const load = React.useCallback(async () => {\n    if (!consentGranted) {\n      throw new Error(\n        `Cannot load script \"${id}\": consent not granted for category \"${category}\"`\n      );\n    }\n    if (isLoaded) return;\n\n    setIsLoading(true);\n    try {\n      await loadScript(id);\n      setIsLoaded(true);\n    } catch (err) {\n      setError(err as Error);\n      throw err;\n    } finally {\n      setIsLoading(false);\n    }\n  }, [consentGranted, isLoaded, id, category]);\n\n  const unload = React.useCallback(() => {\n    unloadScript(id);\n    setIsLoaded(false);\n  }, [id]);\n\n  return {\n    isLoaded,\n    isLoading,\n    hasConsent: consentGranted,\n    error,\n    load,\n    unload,\n  };\n}\n"
    },
    {
      "path": "components/cookie-consent/script-manager.ts",
      "type": "registry:file",
      "target": "components/cookie-consent/script-manager.ts",
      "content": "// Script management utilities for consent-based script loading\n\nimport type { ConsentCategories, ConsentCategory, ScriptConfig } from \"./types\";\n\ninterface ManagedScript extends ScriptConfig {\n  element?: HTMLScriptElement;\n  loaded: boolean;\n}\n\n// Registry of all managed scripts\nconst scriptRegistry = new Map<string, ManagedScript>();\n\n// Track cleanup functions for each script\nconst cleanupRegistry = new Map<string, () => void>();\n\n/**\n * Register a script to be managed by the consent system\n */\nexport function registerScript(config: ScriptConfig): void {\n  if (scriptRegistry.has(config.id)) {\n    console.warn(\n      `[CookieConsent] Script with id \"${config.id}\" is already registered`\n    );\n    return;\n  }\n\n  scriptRegistry.set(config.id, {\n    ...config,\n    loaded: false,\n  });\n}\n\n/**\n * Unregister a script and clean it up\n */\nexport function unregisterScript(id: string): void {\n  const script = scriptRegistry.get(id);\n  if (script) {\n    unloadScript(id);\n    scriptRegistry.delete(id);\n  }\n}\n\n/**\n * Load a script into the DOM\n */\nexport function loadScript(id: string): Promise<void> {\n  return new Promise((resolve, reject) => {\n    const managed = scriptRegistry.get(id);\n    if (!managed) {\n      reject(new Error(`Script \"${id}\" is not registered`));\n      return;\n    }\n\n    if (managed.loaded && managed.element) {\n      resolve();\n      return;\n    }\n\n    const script = document.createElement(\"script\");\n    script.id = `consent-script-${id}`;\n    script.async = true;\n\n    if (managed.src) {\n      script.src = managed.src;\n    } else if (managed.content) {\n      script.textContent = managed.content;\n    } else {\n      reject(new Error(`Script \"${id}\" has no src or content`));\n      return;\n    }\n\n    // Apply custom attributes\n    if (managed.attributes) {\n      Object.entries(managed.attributes).forEach(([key, value]) => {\n        script.setAttribute(key, value);\n      });\n    }\n\n    // For inline scripts (content), resolve immediately after appending\n    // For external scripts (src), wait for onload\n    if (managed.content) {\n      // Inline scripts execute immediately when appended\n      if (managed.strategy === \"beforeInteractive\") {\n        document.head.appendChild(script);\n      } else {\n        document.body.appendChild(script);\n      }\n\n      // Use setTimeout to ensure script has executed\n      setTimeout(() => {\n        managed.loaded = true;\n        managed.element = script;\n        managed.onLoad?.();\n        resolve();\n      }, 0);\n    } else {\n      // External script - wait for load/error events\n      script.onload = () => {\n        managed.loaded = true;\n        managed.element = script;\n        managed.onLoad?.();\n        resolve();\n      };\n\n      script.onerror = () => {\n        const error = new Error(`Failed to load script \"${id}\"`);\n        managed.onError?.(error);\n        reject(error);\n      };\n\n      // Append based on strategy\n      if (managed.strategy === \"beforeInteractive\") {\n        document.head.appendChild(script);\n      } else {\n        document.body.appendChild(script);\n      }\n    }\n  });\n}\n\n/**\n * Unload a script from the DOM and run cleanup\n */\nexport function unloadScript(id: string): void {\n  const managed = scriptRegistry.get(id);\n  if (!managed) return;\n\n  // Remove the script element\n  if (managed.element) {\n    managed.element.remove();\n    managed.element = undefined;\n  }\n\n  // Also try to find by ID in case element reference is stale\n  const existingScript = document.getElementById(`consent-script-${id}`);\n  if (existingScript) {\n    existingScript.remove();\n  }\n\n  // Run the revoke callback\n  managed.onRevoke?.();\n\n  // Run any registered cleanup\n  const cleanup = cleanupRegistry.get(id);\n  if (cleanup) {\n    cleanup();\n    cleanupRegistry.delete(id);\n  }\n\n  managed.loaded = false;\n}\n\n/**\n * Register a cleanup function for a script\n */\nexport function registerCleanup(id: string, cleanup: () => void): void {\n  cleanupRegistry.set(id, cleanup);\n}\n\n/**\n * Load all scripts for consented categories\n */\nexport function loadConsentedScripts(categories: ConsentCategories): void {\n  scriptRegistry.forEach((script, id) => {\n    if (categories[script.category] && !script.loaded) {\n      loadScript(id).catch((error) => {\n        console.error(`[CookieConsent] Failed to load script \"${id}\":`, error);\n      });\n    }\n  });\n}\n\n/**\n * Unload all scripts for revoked categories\n */\nexport function unloadRevokedScripts(\n  previousCategories: ConsentCategories,\n  currentCategories: ConsentCategories\n): ConsentCategory[] {\n  const revokedCategories: ConsentCategory[] = [];\n\n  // Find revoked categories\n  (Object.keys(previousCategories) as ConsentCategory[]).forEach((category) => {\n    if (previousCategories[category] && !currentCategories[category]) {\n      revokedCategories.push(category);\n    }\n  });\n\n  // Unload scripts for revoked categories\n  scriptRegistry.forEach((script, id) => {\n    if (revokedCategories.includes(script.category) && script.loaded) {\n      unloadScript(id);\n    }\n  });\n\n  return revokedCategories;\n}\n\n/**\n * Get all loaded script IDs\n */\nexport function getLoadedScripts(): string[] {\n  const loaded: string[] = [];\n  scriptRegistry.forEach((script, id) => {\n    if (script.loaded) {\n      loaded.push(id);\n    }\n  });\n  return loaded;\n}\n\n/**\n * Get all registered scripts\n */\nexport function getRegisteredScripts(): Map<string, ManagedScript> {\n  return new Map(scriptRegistry);\n}\n\n/**\n * Check if any registered scripts are Google scripts\n */\nexport function hasGoogleScripts(): boolean {\n  for (const script of scriptRegistry.values()) {\n    if (\n      (script.src && (\n        script.src.includes(\"googletagmanager.com\") ||\n        script.src.includes(\"google-analytics.com\") ||\n        script.src.includes(\"googleadservices.com\") ||\n        script.src.includes(\"google.com/analytics\") ||\n        script.src.includes(\"google.com/ads\") ||\n        script.src.includes(\"doubleclick.net\") ||\n        script.src.includes(\"googleapis.com/gtag\")\n      )) ||\n      (script.content && (\n        script.content.includes(\"googletagmanager.com\") ||\n        script.content.includes(\"google-analytics.com\") ||\n        script.content.includes(\"gtag(\") ||\n        script.content.includes(\"dataLayer\") ||\n        script.content.includes(\"ga(\") ||\n        script.content.includes(\"google-analytics\")\n      ))\n    ) {\n      return true;\n    }\n  }\n  return false;\n}\n\n/**\n * Clear all scripts (useful for testing)\n */\nexport function clearAllScripts(): void {\n  scriptRegistry.forEach((_, id) => {\n    unloadScript(id);\n  });\n  scriptRegistry.clear();\n  cleanupRegistry.clear();\n}\n\n/**\n * Common third-party script cleanup helpers\n */\nexport const scriptCleanupHelpers = {\n  // Google Analytics cleanup\n  googleAnalytics: () => {\n    // Remove GA cookies\n    const cookies = document.cookie.split(\";\");\n    cookies.forEach((cookie) => {\n      const name = cookie.split(\"=\")[0].trim();\n      if (\n        name.startsWith(\"_ga\") ||\n        name.startsWith(\"_gid\") ||\n        name.startsWith(\"_gat\")\n      ) {\n        document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=${window.location.hostname}`;\n        document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${window.location.hostname}`;\n      }\n    });\n    // Clear GA global\n    if (typeof window !== \"undefined\") {\n      (window as unknown as Record<string, unknown>).ga = undefined;\n      (window as unknown as Record<string, unknown>).gtag = undefined;\n      (window as unknown as Record<string, unknown>).dataLayer = undefined;\n    }\n  },\n\n  // Facebook Pixel cleanup\n  facebookPixel: () => {\n    const cookies = document.cookie.split(\";\");\n    cookies.forEach((cookie) => {\n      const name = cookie.split(\"=\")[0].trim();\n      if (name.startsWith(\"_fbp\") || name.startsWith(\"_fbc\")) {\n        document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=${window.location.hostname}`;\n        document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${window.location.hostname}`;\n      }\n    });\n    if (typeof window !== \"undefined\") {\n      (window as unknown as Record<string, unknown>).fbq = undefined;\n    }\n  },\n\n  // Generic cookie cleanup by prefix\n  clearCookiesByPrefix: (prefix: string) => {\n    const cookies = document.cookie.split(\";\");\n    cookies.forEach((cookie) => {\n      const name = cookie.split(\"=\")[0].trim();\n      if (name.startsWith(prefix)) {\n        document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=${window.location.hostname}`;\n        document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${window.location.hostname}`;\n      }\n    });\n  },\n};\n"
    },
    {
      "path": "components/cookie-consent/tracker.ts",
      "type": "registry:file",
      "target": "components/cookie-consent/tracker.ts",
      "content": "import type { ConsentAction, ConsentCategories, ConsentRecord, TraceabilityConfig } from \"./types\"\nimport { generateUUID, getVisitorId } from \"./utils\"\n\ninterface TrackConsentParams {\n  categories: ConsentCategories\n  action: ConsentAction\n  consentVersion: string\n  expiresAt: string\n  config: TraceabilityConfig\n  userId?: string\n  scope?: \"device\" | \"global\"\n}\n\n/**\n * Send consent record to the configured endpoint\n */\nexport async function trackConsent(params: TrackConsentParams): Promise<ConsentRecord | null> {\n  const { categories, action, consentVersion, expiresAt, config, userId, scope = \"device\" } = params\n\n  if (!config.enabled || !config.endpoint) {\n    return null\n  }\n\n  const visitorId = config.getVisitorId ? await config.getVisitorId() : getVisitorId()\n\n  const record: ConsentRecord = {\n    visitorId,\n    consentId: generateUUID(),\n    consentVersion,\n    userId,\n    scope,\n    categories,\n    action,\n    timestamp: new Date().toISOString(),\n    expiresAt,\n    url: config.includeUrl !== false && typeof window !== \"undefined\" ? window.location.href : \"\",\n    userAgent: config.includeUserAgent !== false && typeof navigator !== \"undefined\" ? navigator.userAgent : \"\",\n    language: typeof navigator !== \"undefined\" ? navigator.language : \"\",\n  }\n\n  const maxRetries = config.retryOnFailure !== false ? (config.maxRetries ?? 3) : 1\n\n  for (let attempt = 0; attempt < maxRetries; attempt++) {\n    try {\n      const response = await fetch(config.endpoint, {\n        method: config.method ?? \"POST\",\n        headers: {\n          \"Content-Type\": \"application/json\",\n          ...config.headers,\n        },\n        body: JSON.stringify(record),\n      })\n\n      if (!response.ok) {\n        throw new Error(`HTTP ${response.status}: ${response.statusText}`)\n      }\n\n      config.onSuccess?.(record)\n      return record\n    } catch (error) {\n      if (attempt === maxRetries - 1) {\n        const err = error instanceof Error ? error : new Error(String(error))\n        config.onError?.(err, record)\n\n        // Store in localStorage as fallback\n        storeFailedRecord(record)\n      }\n    }\n  }\n\n  return null\n}\n\n/**\n * Store failed record for later retry\n */\nfunction storeFailedRecord(record: ConsentRecord): void {\n  if (typeof window === \"undefined\") return\n\n  const key = \"cookie-consent-pending\"\n  const pending = JSON.parse(localStorage.getItem(key) ?? \"[]\") as ConsentRecord[]\n  pending.push(record)\n  localStorage.setItem(key, JSON.stringify(pending))\n}\n\n/**\n * Retry sending failed records\n */\nexport async function retryFailedRecords(config: TraceabilityConfig): Promise<void> {\n  if (typeof window === \"undefined\" || !config.enabled || !config.endpoint) return\n\n  const key = \"cookie-consent-pending\"\n  const pending = JSON.parse(localStorage.getItem(key) ?? \"[]\") as ConsentRecord[]\n\n  if (pending.length === 0) return\n\n  const stillPending: ConsentRecord[] = []\n\n  for (const record of pending) {\n    try {\n      const response = await fetch(config.endpoint, {\n        method: config.method ?? \"POST\",\n        headers: {\n          \"Content-Type\": \"application/json\",\n          ...config.headers,\n        },\n        body: JSON.stringify(record),\n      })\n\n      if (!response.ok) {\n        stillPending.push(record)\n      } else {\n        config.onSuccess?.(record)\n      }\n    } catch {\n      stillPending.push(record)\n    }\n  }\n\n  localStorage.setItem(key, JSON.stringify(stillPending))\n}\n"
    }
  ],
  "description": "A full-featured, GDPR-compliant cookie consent solution with traceability support, script management, and granular category control.",
  "category": "forms",
  "subcategory": "consent"
}