admesh-ui-sdk
Version:
Beautiful, modern React components for displaying AI-powered product recommendations with citation-based conversation ads, auto-triggered widgets, floating chat, conversational interfaces, persistent sidebar, and built-in tracking
1 lines • 218 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","sources":["../node_modules/react/cjs/react-jsx-runtime.production.js","../node_modules/react/cjs/react-jsx-runtime.development.js","../node_modules/react/jsx-runtime.js","../node_modules/classnames/index.js","../src/hooks/useAdMeshTracker.ts","../src/components/AdMeshLinkTracker.tsx","../src/components/AdMeshProductCard.tsx","../src/components/AdMeshCompareTable.tsx","../src/components/AdMeshBadge.tsx","../src/hooks/useAdMeshStyles.ts","../src/components/AdMeshLayout.tsx","../src/components/AdMeshInlineRecommendation.tsx","../src/components/AdMeshConversationSummary.tsx","../src/components/AdMeshCitationReference.tsx","../src/components/AdMeshCitationUnit.tsx","../src/components/AdMeshConversationalUnit.tsx","../src/components/AdMeshSidebarHeader.tsx","../src/components/AdMeshSidebarContent.tsx","../src/components/AdMeshSidebar.tsx","../src/components/AdMeshChatMessage.tsx","../src/components/AdMeshChatInput.tsx","../src/components/AdMeshChatInterface.tsx","../src/components/AdMeshFloatingChat.tsx","../src/components/AdMeshAutoRecommendationWidget.tsx","../src/index.ts"],"sourcesContent":["/**\n * @license React\n * react-jsx-runtime.production.js\n *\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\"use strict\";\nvar REACT_ELEMENT_TYPE = Symbol.for(\"react.transitional.element\"),\n REACT_FRAGMENT_TYPE = Symbol.for(\"react.fragment\");\nfunction jsxProd(type, config, maybeKey) {\n var key = null;\n void 0 !== maybeKey && (key = \"\" + maybeKey);\n void 0 !== config.key && (key = \"\" + config.key);\n if (\"key\" in config) {\n maybeKey = {};\n for (var propName in config)\n \"key\" !== propName && (maybeKey[propName] = config[propName]);\n } else maybeKey = config;\n config = maybeKey.ref;\n return {\n $$typeof: REACT_ELEMENT_TYPE,\n type: type,\n key: key,\n ref: void 0 !== config ? config : null,\n props: maybeKey\n };\n}\nexports.Fragment = REACT_FRAGMENT_TYPE;\nexports.jsx = jsxProd;\nexports.jsxs = jsxProd;\n","/**\n * @license React\n * react-jsx-runtime.development.js\n *\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\"use strict\";\n\"production\" !== process.env.NODE_ENV &&\n (function () {\n function getComponentNameFromType(type) {\n if (null == type) return null;\n if (\"function\" === typeof type)\n return type.$$typeof === REACT_CLIENT_REFERENCE\n ? null\n : type.displayName || type.name || null;\n if (\"string\" === typeof type) return type;\n switch (type) {\n case REACT_FRAGMENT_TYPE:\n return \"Fragment\";\n case REACT_PROFILER_TYPE:\n return \"Profiler\";\n case REACT_STRICT_MODE_TYPE:\n return \"StrictMode\";\n case REACT_SUSPENSE_TYPE:\n return \"Suspense\";\n case REACT_SUSPENSE_LIST_TYPE:\n return \"SuspenseList\";\n case REACT_ACTIVITY_TYPE:\n return \"Activity\";\n }\n if (\"object\" === typeof type)\n switch (\n (\"number\" === typeof type.tag &&\n console.error(\n \"Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue.\"\n ),\n type.$$typeof)\n ) {\n case REACT_PORTAL_TYPE:\n return \"Portal\";\n case REACT_CONTEXT_TYPE:\n return (type.displayName || \"Context\") + \".Provider\";\n case REACT_CONSUMER_TYPE:\n return (type._context.displayName || \"Context\") + \".Consumer\";\n case REACT_FORWARD_REF_TYPE:\n var innerType = type.render;\n type = type.displayName;\n type ||\n ((type = innerType.displayName || innerType.name || \"\"),\n (type = \"\" !== type ? \"ForwardRef(\" + type + \")\" : \"ForwardRef\"));\n return type;\n case REACT_MEMO_TYPE:\n return (\n (innerType = type.displayName || null),\n null !== innerType\n ? innerType\n : getComponentNameFromType(type.type) || \"Memo\"\n );\n case REACT_LAZY_TYPE:\n innerType = type._payload;\n type = type._init;\n try {\n return getComponentNameFromType(type(innerType));\n } catch (x) {}\n }\n return null;\n }\n function testStringCoercion(value) {\n return \"\" + value;\n }\n function checkKeyStringCoercion(value) {\n try {\n testStringCoercion(value);\n var JSCompiler_inline_result = !1;\n } catch (e) {\n JSCompiler_inline_result = !0;\n }\n if (JSCompiler_inline_result) {\n JSCompiler_inline_result = console;\n var JSCompiler_temp_const = JSCompiler_inline_result.error;\n var JSCompiler_inline_result$jscomp$0 =\n (\"function\" === typeof Symbol &&\n Symbol.toStringTag &&\n value[Symbol.toStringTag]) ||\n value.constructor.name ||\n \"Object\";\n JSCompiler_temp_const.call(\n JSCompiler_inline_result,\n \"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.\",\n JSCompiler_inline_result$jscomp$0\n );\n return testStringCoercion(value);\n }\n }\n function getTaskName(type) {\n if (type === REACT_FRAGMENT_TYPE) return \"<>\";\n if (\n \"object\" === typeof type &&\n null !== type &&\n type.$$typeof === REACT_LAZY_TYPE\n )\n return \"<...>\";\n try {\n var name = getComponentNameFromType(type);\n return name ? \"<\" + name + \">\" : \"<...>\";\n } catch (x) {\n return \"<...>\";\n }\n }\n function getOwner() {\n var dispatcher = ReactSharedInternals.A;\n return null === dispatcher ? null : dispatcher.getOwner();\n }\n function UnknownOwner() {\n return Error(\"react-stack-top-frame\");\n }\n function hasValidKey(config) {\n if (hasOwnProperty.call(config, \"key\")) {\n var getter = Object.getOwnPropertyDescriptor(config, \"key\").get;\n if (getter && getter.isReactWarning) return !1;\n }\n return void 0 !== config.key;\n }\n function defineKeyPropWarningGetter(props, displayName) {\n function warnAboutAccessingKey() {\n specialPropKeyWarningShown ||\n ((specialPropKeyWarningShown = !0),\n console.error(\n \"%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)\",\n displayName\n ));\n }\n warnAboutAccessingKey.isReactWarning = !0;\n Object.defineProperty(props, \"key\", {\n get: warnAboutAccessingKey,\n configurable: !0\n });\n }\n function elementRefGetterWithDeprecationWarning() {\n var componentName = getComponentNameFromType(this.type);\n didWarnAboutElementRef[componentName] ||\n ((didWarnAboutElementRef[componentName] = !0),\n console.error(\n \"Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.\"\n ));\n componentName = this.props.ref;\n return void 0 !== componentName ? componentName : null;\n }\n function ReactElement(\n type,\n key,\n self,\n source,\n owner,\n props,\n debugStack,\n debugTask\n ) {\n self = props.ref;\n type = {\n $$typeof: REACT_ELEMENT_TYPE,\n type: type,\n key: key,\n props: props,\n _owner: owner\n };\n null !== (void 0 !== self ? self : null)\n ? Object.defineProperty(type, \"ref\", {\n enumerable: !1,\n get: elementRefGetterWithDeprecationWarning\n })\n : Object.defineProperty(type, \"ref\", { enumerable: !1, value: null });\n type._store = {};\n Object.defineProperty(type._store, \"validated\", {\n configurable: !1,\n enumerable: !1,\n writable: !0,\n value: 0\n });\n Object.defineProperty(type, \"_debugInfo\", {\n configurable: !1,\n enumerable: !1,\n writable: !0,\n value: null\n });\n Object.defineProperty(type, \"_debugStack\", {\n configurable: !1,\n enumerable: !1,\n writable: !0,\n value: debugStack\n });\n Object.defineProperty(type, \"_debugTask\", {\n configurable: !1,\n enumerable: !1,\n writable: !0,\n value: debugTask\n });\n Object.freeze && (Object.freeze(type.props), Object.freeze(type));\n return type;\n }\n function jsxDEVImpl(\n type,\n config,\n maybeKey,\n isStaticChildren,\n source,\n self,\n debugStack,\n debugTask\n ) {\n var children = config.children;\n if (void 0 !== children)\n if (isStaticChildren)\n if (isArrayImpl(children)) {\n for (\n isStaticChildren = 0;\n isStaticChildren < children.length;\n isStaticChildren++\n )\n validateChildKeys(children[isStaticChildren]);\n Object.freeze && Object.freeze(children);\n } else\n console.error(\n \"React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.\"\n );\n else validateChildKeys(children);\n if (hasOwnProperty.call(config, \"key\")) {\n children = getComponentNameFromType(type);\n var keys = Object.keys(config).filter(function (k) {\n return \"key\" !== k;\n });\n isStaticChildren =\n 0 < keys.length\n ? \"{key: someKey, \" + keys.join(\": ..., \") + \": ...}\"\n : \"{key: someKey}\";\n didWarnAboutKeySpread[children + isStaticChildren] ||\n ((keys =\n 0 < keys.length ? \"{\" + keys.join(\": ..., \") + \": ...}\" : \"{}\"),\n console.error(\n 'A props object containing a \"key\" prop is being spread into JSX:\\n let props = %s;\\n <%s {...props} />\\nReact keys must be passed directly to JSX without using spread:\\n let props = %s;\\n <%s key={someKey} {...props} />',\n isStaticChildren,\n children,\n keys,\n children\n ),\n (didWarnAboutKeySpread[children + isStaticChildren] = !0));\n }\n children = null;\n void 0 !== maybeKey &&\n (checkKeyStringCoercion(maybeKey), (children = \"\" + maybeKey));\n hasValidKey(config) &&\n (checkKeyStringCoercion(config.key), (children = \"\" + config.key));\n if (\"key\" in config) {\n maybeKey = {};\n for (var propName in config)\n \"key\" !== propName && (maybeKey[propName] = config[propName]);\n } else maybeKey = config;\n children &&\n defineKeyPropWarningGetter(\n maybeKey,\n \"function\" === typeof type\n ? type.displayName || type.name || \"Unknown\"\n : type\n );\n return ReactElement(\n type,\n children,\n self,\n source,\n getOwner(),\n maybeKey,\n debugStack,\n debugTask\n );\n }\n function validateChildKeys(node) {\n \"object\" === typeof node &&\n null !== node &&\n node.$$typeof === REACT_ELEMENT_TYPE &&\n node._store &&\n (node._store.validated = 1);\n }\n var React = require(\"react\"),\n REACT_ELEMENT_TYPE = Symbol.for(\"react.transitional.element\"),\n REACT_PORTAL_TYPE = Symbol.for(\"react.portal\"),\n REACT_FRAGMENT_TYPE = Symbol.for(\"react.fragment\"),\n REACT_STRICT_MODE_TYPE = Symbol.for(\"react.strict_mode\"),\n REACT_PROFILER_TYPE = Symbol.for(\"react.profiler\");\n Symbol.for(\"react.provider\");\n var REACT_CONSUMER_TYPE = Symbol.for(\"react.consumer\"),\n REACT_CONTEXT_TYPE = Symbol.for(\"react.context\"),\n REACT_FORWARD_REF_TYPE = Symbol.for(\"react.forward_ref\"),\n REACT_SUSPENSE_TYPE = Symbol.for(\"react.suspense\"),\n REACT_SUSPENSE_LIST_TYPE = Symbol.for(\"react.suspense_list\"),\n REACT_MEMO_TYPE = Symbol.for(\"react.memo\"),\n REACT_LAZY_TYPE = Symbol.for(\"react.lazy\"),\n REACT_ACTIVITY_TYPE = Symbol.for(\"react.activity\"),\n REACT_CLIENT_REFERENCE = Symbol.for(\"react.client.reference\"),\n ReactSharedInternals =\n React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,\n hasOwnProperty = Object.prototype.hasOwnProperty,\n isArrayImpl = Array.isArray,\n createTask = console.createTask\n ? console.createTask\n : function () {\n return null;\n };\n React = {\n \"react-stack-bottom-frame\": function (callStackForError) {\n return callStackForError();\n }\n };\n var specialPropKeyWarningShown;\n var didWarnAboutElementRef = {};\n var unknownOwnerDebugStack = React[\"react-stack-bottom-frame\"].bind(\n React,\n UnknownOwner\n )();\n var unknownOwnerDebugTask = createTask(getTaskName(UnknownOwner));\n var didWarnAboutKeySpread = {};\n exports.Fragment = REACT_FRAGMENT_TYPE;\n exports.jsx = function (type, config, maybeKey, source, self) {\n var trackActualOwner =\n 1e4 > ReactSharedInternals.recentlyCreatedOwnerStacks++;\n return jsxDEVImpl(\n type,\n config,\n maybeKey,\n !1,\n source,\n self,\n trackActualOwner\n ? Error(\"react-stack-top-frame\")\n : unknownOwnerDebugStack,\n trackActualOwner ? createTask(getTaskName(type)) : unknownOwnerDebugTask\n );\n };\n exports.jsxs = function (type, config, maybeKey, source, self) {\n var trackActualOwner =\n 1e4 > ReactSharedInternals.recentlyCreatedOwnerStacks++;\n return jsxDEVImpl(\n type,\n config,\n maybeKey,\n !0,\n source,\n self,\n trackActualOwner\n ? Error(\"react-stack-top-frame\")\n : unknownOwnerDebugStack,\n trackActualOwner ? createTask(getTaskName(type)) : unknownOwnerDebugTask\n );\n };\n })();\n","'use strict';\n\nif (process.env.NODE_ENV === 'production') {\n module.exports = require('./cjs/react-jsx-runtime.production.js');\n} else {\n module.exports = require('./cjs/react-jsx-runtime.development.js');\n}\n","/*!\n\tCopyright (c) 2018 Jed Watson.\n\tLicensed under the MIT License (MIT), see\n\thttp://jedwatson.github.io/classnames\n*/\n/* global define */\n\n(function () {\n\t'use strict';\n\n\tvar hasOwn = {}.hasOwnProperty;\n\n\tfunction classNames () {\n\t\tvar classes = '';\n\n\t\tfor (var i = 0; i < arguments.length; i++) {\n\t\t\tvar arg = arguments[i];\n\t\t\tif (arg) {\n\t\t\t\tclasses = appendClass(classes, parseValue(arg));\n\t\t\t}\n\t\t}\n\n\t\treturn classes;\n\t}\n\n\tfunction parseValue (arg) {\n\t\tif (typeof arg === 'string' || typeof arg === 'number') {\n\t\t\treturn arg;\n\t\t}\n\n\t\tif (typeof arg !== 'object') {\n\t\t\treturn '';\n\t\t}\n\n\t\tif (Array.isArray(arg)) {\n\t\t\treturn classNames.apply(null, arg);\n\t\t}\n\n\t\tif (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes('[native code]')) {\n\t\t\treturn arg.toString();\n\t\t}\n\n\t\tvar classes = '';\n\n\t\tfor (var key in arg) {\n\t\t\tif (hasOwn.call(arg, key) && arg[key]) {\n\t\t\t\tclasses = appendClass(classes, key);\n\t\t\t}\n\t\t}\n\n\t\treturn classes;\n\t}\n\n\tfunction appendClass (value, newClass) {\n\t\tif (!newClass) {\n\t\t\treturn value;\n\t\t}\n\t\n\t\tif (value) {\n\t\t\treturn value + ' ' + newClass;\n\t\t}\n\t\n\t\treturn value + newClass;\n\t}\n\n\tif (typeof module !== 'undefined' && module.exports) {\n\t\tclassNames.default = classNames;\n\t\tmodule.exports = classNames;\n\t} else if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {\n\t\t// register as 'classnames', consistent with npm package name\n\t\tdefine('classnames', [], function () {\n\t\t\treturn classNames;\n\t\t});\n\t} else {\n\t\twindow.classNames = classNames;\n\t}\n}());\n","import { useState, useCallback, useMemo } from 'react';\nimport type { TrackingData, UseAdMeshTrackerReturn } from '../types/index';\n\n// Default tracking endpoint - can be overridden via config\nconst DEFAULT_TRACKING_URL = 'https://api.useadmesh.com/track';\n\ninterface TrackingConfig {\n apiBaseUrl?: string;\n enabled?: boolean;\n debug?: boolean;\n retryAttempts?: number;\n retryDelay?: number;\n}\n\n// Global config that can be set by the consuming application\nlet globalConfig: TrackingConfig = {\n apiBaseUrl: DEFAULT_TRACKING_URL,\n enabled: true,\n debug: false,\n retryAttempts: 3,\n retryDelay: 1000\n};\n\nexport const setAdMeshTrackerConfig = (config: Partial<TrackingConfig>) => {\n globalConfig = { ...globalConfig, ...config };\n};\n\nexport const useAdMeshTracker = (config?: Partial<TrackingConfig>): UseAdMeshTrackerReturn => {\n const [isTracking, setIsTracking] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const mergedConfig = useMemo(() => ({ ...globalConfig, ...config }), [config]);\n\n const log = useCallback((message: string, data?: unknown) => {\n if (mergedConfig.debug) {\n console.log(`[AdMesh Tracker] ${message}`, data);\n }\n }, [mergedConfig.debug]);\n\n const sendTrackingEvent = useCallback(async (\n eventType: 'click' | 'view' | 'conversion',\n data: TrackingData\n ): Promise<void> => {\n if (!mergedConfig.enabled) {\n log('Tracking disabled, skipping event', { eventType, data });\n return;\n }\n\n if (!data.adId || !data.admeshLink) {\n const errorMsg = 'Missing required tracking data: adId and admeshLink are required';\n log(errorMsg, data);\n setError(errorMsg);\n return;\n }\n\n setIsTracking(true);\n setError(null);\n\n const payload = {\n event_type: eventType,\n ad_id: data.adId,\n admesh_link: data.admeshLink,\n product_id: data.productId,\n user_id: data.userId,\n session_id: data.sessionId,\n revenue: data.revenue,\n conversion_type: data.conversionType,\n metadata: data.metadata,\n timestamp: new Date().toISOString(),\n user_agent: navigator.userAgent,\n referrer: document.referrer,\n page_url: window.location.href\n };\n\n log(`Sending ${eventType} event`, payload);\n\n let lastError: Error | null = null;\n \n for (let attempt = 1; attempt <= (mergedConfig.retryAttempts || 3); attempt++) {\n try {\n const response = await fetch(`${mergedConfig.apiBaseUrl}/events`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const result = await response.json();\n log(`${eventType} event tracked successfully`, result);\n setIsTracking(false);\n return;\n\n } catch (err) {\n lastError = err as Error;\n log(`Attempt ${attempt} failed for ${eventType} event`, err);\n \n if (attempt < (mergedConfig.retryAttempts || 3)) {\n await new Promise(resolve => \n setTimeout(resolve, (mergedConfig.retryDelay || 1000) * attempt)\n );\n }\n }\n }\n\n // All attempts failed\n const errorMsg = `Failed to track ${eventType} event after ${mergedConfig.retryAttempts} attempts: ${lastError?.message}`;\n log(errorMsg, lastError);\n setError(errorMsg);\n setIsTracking(false);\n }, [mergedConfig, log]);\n\n const trackClick = useCallback(async (data: TrackingData): Promise<void> => {\n return sendTrackingEvent('click', data);\n }, [sendTrackingEvent]);\n\n const trackView = useCallback(async (data: TrackingData): Promise<void> => {\n return sendTrackingEvent('view', data);\n }, [sendTrackingEvent]);\n\n const trackConversion = useCallback(async (data: TrackingData): Promise<void> => {\n if (!data.revenue && !data.conversionType) {\n log('Warning: Conversion tracking without revenue or conversion type', data);\n }\n return sendTrackingEvent('conversion', data);\n }, [sendTrackingEvent, log]);\n\n return {\n trackClick,\n trackView,\n trackConversion,\n isTracking,\n error\n };\n};\n\n// Utility function to build admesh_link with tracking parameters\nexport const buildAdMeshLink = (\n baseLink: string, \n adId: string, \n additionalParams?: Record<string, string>\n): string => {\n try {\n const url = new URL(baseLink);\n url.searchParams.set('ad_id', adId);\n url.searchParams.set('utm_source', 'admesh');\n url.searchParams.set('utm_medium', 'recommendation');\n \n if (additionalParams) {\n Object.entries(additionalParams).forEach(([key, value]) => {\n url.searchParams.set(key, value);\n });\n }\n \n return url.toString();\n } catch (err) {\n console.warn('[AdMesh] Invalid URL provided to buildAdMeshLink:', baseLink, err);\n return baseLink;\n }\n};\n\n// Helper function to extract tracking data from recommendation\nexport const extractTrackingData = (\n recommendation: { ad_id: string; admesh_link: string; product_id: string },\n additionalData?: Partial<TrackingData>\n): TrackingData => {\n return {\n adId: recommendation.ad_id,\n admeshLink: recommendation.admesh_link,\n productId: recommendation.product_id,\n ...additionalData\n };\n};\n","import React, { useCallback, useEffect, useRef } from 'react';\nimport type { AdMeshLinkTrackerProps } from '../types/index';\nimport { useAdMeshTracker } from '../hooks/useAdMeshTracker';\n\nexport const AdMeshLinkTracker: React.FC<AdMeshLinkTrackerProps> = ({\n adId,\n admeshLink,\n productId,\n children,\n onClick,\n trackingData,\n className\n}) => {\n const { trackClick, trackView } = useAdMeshTracker();\n const elementRef = useRef<HTMLDivElement>(null);\n const hasTrackedView = useRef(false);\n\n // Track view when component becomes visible\n useEffect(() => {\n if (!elementRef.current || hasTrackedView.current) return;\n\n const observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n if (entry.isIntersecting && !hasTrackedView.current) {\n hasTrackedView.current = true;\n trackView({\n adId,\n admeshLink,\n productId,\n ...trackingData\n }).catch(console.error);\n }\n });\n },\n {\n threshold: 0.5, // Track when 50% of the element is visible\n rootMargin: '0px'\n }\n );\n\n observer.observe(elementRef.current);\n\n return () => {\n observer.disconnect();\n };\n }, [adId, admeshLink, productId, trackingData, trackView]);\n\n const handleClick = useCallback(async (event: React.MouseEvent) => {\n // Track the click\n try {\n await trackClick({\n adId,\n admeshLink,\n productId,\n ...trackingData\n });\n } catch (error) {\n console.error('Failed to track click:', error);\n }\n\n // Call custom onClick handler if provided\n if (onClick) {\n onClick();\n }\n\n // If the children contain a link, let the browser handle navigation\n // Otherwise, navigate programmatically\n const target = event.target as HTMLElement;\n const link = target.closest('a');\n \n if (!link) {\n // No link found, navigate programmatically\n window.open(admeshLink, '_blank', 'noopener,noreferrer');\n }\n // If there's a link, let the browser handle it naturally\n }, [adId, admeshLink, productId, trackingData, trackClick, onClick]);\n\n return (\n <div\n ref={elementRef}\n className={className}\n onClick={handleClick}\n style={{ cursor: 'pointer' }}\n >\n {children}\n </div>\n );\n};\n\nAdMeshLinkTracker.displayName = 'AdMeshLinkTracker';\n","import React, { useMemo } from 'react';\nimport classNames from 'classnames';\nimport type { AdMeshProductCardProps, BadgeType } from '../types/index';\nimport { AdMeshLinkTracker } from './AdMeshLinkTracker';\n\nexport const AdMeshProductCard: React.FC<AdMeshProductCardProps> = ({\n recommendation,\n theme,\n showMatchScore = true,\n showBadges = true,\n onClick,\n className\n}) => {\n // Generate badges based on recommendation data\n const badges = useMemo((): BadgeType[] => {\n const generatedBadges: BadgeType[] = [];\n \n // Add Top Match badge for high match scores\n if (recommendation.intent_match_score >= 0.8) {\n generatedBadges.push('Top Match');\n }\n \n // Add Free Tier badge\n if (recommendation.has_free_tier) {\n generatedBadges.push('Free Tier');\n }\n \n // Add Trial Available badge\n if (recommendation.trial_days && recommendation.trial_days > 0) {\n generatedBadges.push('Trial Available');\n }\n \n // Add AI Powered badge (check if AI-related keywords exist)\n const aiKeywords = ['ai', 'artificial intelligence', 'machine learning', 'ml', 'automation'];\n const hasAIKeywords = recommendation.keywords?.some(keyword => \n aiKeywords.some(ai => keyword.toLowerCase().includes(ai))\n ) || recommendation.title.toLowerCase().includes('ai');\n \n if (hasAIKeywords) {\n generatedBadges.push('AI Powered');\n }\n \n return generatedBadges;\n }, [recommendation]);\n\n // Format match score as percentage\n const matchScorePercentage = Math.round(recommendation.intent_match_score * 100);\n\n const cardClasses = classNames(\n 'admesh-component',\n 'admesh-card',\n 'relative p-3 rounded-lg bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-700 shadow-sm hover:shadow transition-shadow cursor-pointer',\n className\n );\n\n const cardStyle = theme?.accentColor ? {\n '--admesh-primary': theme.accentColor,\n '--admesh-primary-hover': theme.accentColor + 'dd', // Add some transparency for hover\n } as React.CSSProperties : undefined;\n\n return (\n <AdMeshLinkTracker\n adId={recommendation.ad_id}\n admeshLink={recommendation.admesh_link}\n productId={recommendation.product_id}\n onClick={() => onClick?.(recommendation.ad_id, recommendation.admesh_link)}\n trackingData={{\n title: recommendation.title,\n matchScore: recommendation.intent_match_score\n }}\n className={cardClasses}\n >\n <div\n className=\"h-full flex flex-col\"\n style={cardStyle}\n data-admesh-theme={theme?.mode}\n >\n {/* Header with badges and title */}\n <div className=\"flex justify-between items-start mb-2\">\n <div className=\"flex items-center gap-2\">\n {showBadges && badges.includes('Top Match') && (\n <span className=\"text-xs font-semibold text-white bg-black px-2 py-0.5 rounded-full\">\n Top Match\n </span>\n )}\n <h4 className=\"font-semibold text-gray-800 dark:text-gray-200\">\n {recommendation.title}\n </h4>\n\n <div className=\"flex gap-2\">\n <button className=\"text-xs px-2 py-1 rounded-full bg-black text-white hover:bg-gray-800 flex items-center\">\n Visit\n <svg className=\"ml-1 h-3 w-3\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14\" />\n </svg>\n </button>\n </div>\n </div>\n </div>\n\n <p className=\"text-sm text-gray-700 dark:text-gray-300 mb-3\">\n {recommendation.reason}\n </p>\n\n {/* Confidence Score */}\n {showMatchScore && typeof recommendation.intent_match_score === \"number\" && (\n <div className=\"mb-3\">\n <div className=\"flex items-center justify-between text-xs text-gray-500 dark:text-gray-400 mb-1\">\n <span>Confidence</span>\n <span>{matchScorePercentage}%</span>\n </div>\n <div className=\"w-full bg-gray-200 dark:bg-slate-600 rounded h-1.5 overflow-hidden\">\n <div\n className=\"bg-black h-1.5\"\n style={{ width: `${matchScorePercentage}%` }}\n />\n </div>\n </div>\n )}\n\n <div className=\"flex flex-wrap gap-2 text-xs mb-2\">\n {recommendation.pricing && (\n <span className=\"flex items-center text-gray-600 dark:text-gray-400\">\n <svg className=\"h-3 w-3 mr-1\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1\" />\n </svg>\n {recommendation.pricing}\n </span>\n )}\n\n {recommendation.has_free_tier && (\n <span className=\"flex items-center px-1.5 py-0.5 bg-gray-100 dark:bg-gray-900/30 text-gray-700 dark:text-gray-400 rounded-full\">\n <svg className=\"h-3 w-3 mr-1\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 8v13m0-13V6a2 2 0 112 2h-2zm0 0V5.5A2.5 2.5 0 109.5 8H12zm-7 4h14M5 12a2 2 0 110-4h14a2 2 0 110 4M5 12v7a2 2 0 002 2h10a2 2 0 002-2v-7\" />\n </svg>\n Free Tier\n </span>\n )}\n\n {recommendation.trial_days && recommendation.trial_days > 0 && (\n <span className=\"flex items-center text-gray-600 dark:text-gray-400\">\n <svg className=\"h-3 w-3 mr-1\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M8 7V3a2 2 0 012-2h4a2 2 0 012 2v4m-6 4v10m6-10v10m-6 0h6\" />\n </svg>\n {recommendation.trial_days}-day trial\n </span>\n )}\n </div>\n\n {/* Features */}\n {recommendation.features && recommendation.features.length > 0 && (\n <div className=\"mb-2\">\n <div className=\"text-xs text-gray-500 dark:text-gray-400 mb-1\">\n Features:\n </div>\n <div className=\"flex flex-wrap gap-1.5\">\n {recommendation.features.map((feature, j) => (\n <span\n key={j}\n className=\"text-xs px-2 py-0.5 rounded-full bg-gray-100 dark:bg-slate-700 text-gray-700 dark:text-gray-300\"\n >\n <svg className=\"h-3 w-3 mr-0.5 inline text-gray-500\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n {feature}\n </span>\n ))}\n </div>\n </div>\n )}\n\n {/* Integrations */}\n {recommendation.integrations && recommendation.integrations.length > 0 && (\n <div className=\"mb-2\">\n <div className=\"text-xs text-gray-500 dark:text-gray-400 mb-1\">\n Integrates with:\n </div>\n <div className=\"flex flex-wrap gap-1.5\">\n {recommendation.integrations.map((integration, j) => (\n <span\n key={j}\n className=\"text-xs px-2 py-0.5 rounded-full bg-gray-100 dark:bg-gray-900/30 text-gray-700 dark:text-gray-300\"\n >\n <svg className=\"h-3 w-3 mr-0.5 inline\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10\" />\n </svg>\n {integration}\n </span>\n ))}\n </div>\n </div>\n )}\n\n {/* Reviews summary */}\n {recommendation.reviews_summary && (\n <div className=\"text-xs text-gray-600 dark:text-gray-400 mt-2\">\n {recommendation.reviews_summary}\n </div>\n )}\n\n {/* Powered by AdMesh branding */}\n <div className=\"flex justify-end mt-auto pt-2\">\n <span className=\"text-xs text-gray-400 dark:text-gray-500\">\n Powered by AdMesh\n </span>\n </div>\n </div>\n </AdMeshLinkTracker>\n );\n};\n\nAdMeshProductCard.displayName = 'AdMeshProductCard';\n","import React, { useMemo } from 'react';\nimport classNames from 'classnames';\nimport type { AdMeshCompareTableProps } from '../types/index';\nimport { AdMeshLinkTracker } from './AdMeshLinkTracker';\n\nexport const AdMeshCompareTable: React.FC<AdMeshCompareTableProps> = ({\n recommendations,\n theme,\n maxProducts = 3,\n showMatchScores = true,\n showFeatures = true,\n onProductClick,\n className\n}) => {\n // Limit the number of products to compare\n const productsToCompare = useMemo(() => {\n return recommendations.slice(0, maxProducts);\n }, [recommendations, maxProducts]);\n\n\n\n const containerClasses = classNames(\n 'admesh-component',\n 'admesh-compare-layout',\n className\n );\n\n const containerStyle = theme?.accentColor ? {\n '--admesh-primary': theme.accentColor,\n } as React.CSSProperties : undefined;\n\n if (productsToCompare.length === 0) {\n return (\n <div className={containerClasses}>\n <div className=\"p-8 text-center text-gray-500 dark:text-gray-400\">\n <p>No products to compare</p>\n </div>\n </div>\n );\n }\n\n return (\n <div\n className={containerClasses}\n style={containerStyle}\n data-admesh-theme={theme?.mode}\n >\n <div className=\"space-y-6\">\n {/* Header */}\n <div className=\"text-center\">\n <div className=\"flex items-center justify-center gap-2 mb-2\">\n <svg className=\"w-5 h-5 text-gray-600 dark:text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z\" />\n </svg>\n <h3 className=\"text-lg font-semibold text-gray-800 dark:text-gray-200\">\n Smart Comparison\n </h3>\n </div>\n <p className=\"text-sm text-gray-600 dark:text-gray-400\">\n {productsToCompare.length} intelligent matches found\n </p>\n </div>\n\n {/* Product Cards Grid */}\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4\">\n {productsToCompare.map((product, index) => (\n <AdMeshLinkTracker\n key={product.product_id || index}\n adId={product.ad_id}\n admeshLink={product.admesh_link}\n productId={product.product_id}\n onClick={() => onProductClick?.(product.ad_id, product.admesh_link)}\n className=\"relative p-4 rounded-lg bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-700 shadow-sm hover:shadow transition-shadow\"\n >\n {/* Product Header */}\n <div className=\"flex justify-between items-start mb-3\">\n <div className=\"flex items-center gap-2\">\n {index === 0 && (\n <span className=\"text-xs font-semibold text-white bg-black px-2 py-0.5 rounded-full\">\n Top Match\n </span>\n )}\n <span className=\"text-xs text-gray-400 dark:text-gray-500\">\n #{index + 1}\n </span>\n </div>\n {showMatchScores && (\n <div className=\"text-xs text-gray-500 dark:text-gray-400\">\n {Math.round(product.intent_match_score * 100)}% match\n </div>\n )}\n </div>\n\n {/* Product Title */}\n <h4 className=\"font-semibold text-gray-800 dark:text-gray-200 mb-2\">\n {product.title}\n </h4>\n\n {/* Confidence Score */}\n {showMatchScores && (\n <div className=\"mb-3\">\n <div className=\"flex items-center justify-between text-xs text-gray-500 dark:text-gray-400 mb-1\">\n <span>Confidence</span>\n <span>{Math.round(product.intent_match_score * 100)}%</span>\n </div>\n <div className=\"w-full bg-gray-200 dark:bg-slate-600 rounded h-1.5 overflow-hidden\">\n <div\n className=\"bg-black h-1.5\"\n style={{ width: `${Math.round(product.intent_match_score * 100)}%` }}\n />\n </div>\n </div>\n )}\n\n {/* Pricing and Trial Info */}\n <div className=\"flex flex-wrap gap-2 text-xs mb-3\">\n {product.pricing && (\n <span className=\"flex items-center text-gray-600 dark:text-gray-400\">\n <svg className=\"h-3 w-3 mr-1\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1\" />\n </svg>\n {product.pricing}\n </span>\n )}\n\n {product.has_free_tier && (\n <span className=\"flex items-center px-1.5 py-0.5 bg-gray-100 dark:bg-gray-900/30 text-gray-700 dark:text-gray-400 rounded-full\">\n <svg className=\"h-3 w-3 mr-1\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 8v13m0-13V6a2 2 0 112 2h-2zm0 0V5.5A2.5 2.5 0 109.5 8H12zm-7 4h14M5 12a2 2 0 110-4h14a2 2 0 110 4M5 12v7a2 2 0 002 2h10a2 2 0 002-2v-7\" />\n </svg>\n Free Tier\n </span>\n )}\n\n {product.trial_days && product.trial_days > 0 && (\n <span className=\"flex items-center text-gray-600 dark:text-gray-400\">\n <svg className=\"h-3 w-3 mr-1\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M8 7V3a2 2 0 012-2h4a2 2 0 012 2v4m-6 4v10m6-10v10m-6 0h6\" />\n </svg>\n {product.trial_days}-day trial\n </span>\n )}\n </div>\n\n {/* Features */}\n {showFeatures && product.features && product.features.length > 0 && (\n <div className=\"mb-3\">\n <div className=\"text-xs text-gray-500 dark:text-gray-400 mb-1\">\n Key Features:\n </div>\n <div className=\"flex flex-wrap gap-1.5\">\n {product.features.slice(0, 4).map((feature, j) => (\n <span\n key={j}\n className=\"text-xs px-2 py-0.5 rounded-full bg-gray-100 dark:bg-slate-700 text-gray-700 dark:text-gray-300\"\n >\n <svg className=\"h-3 w-3 mr-0.5 inline text-gray-500\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n {feature}\n </span>\n ))}\n {(product.features.length || 0) > 4 && (\n <span className=\"text-xs text-gray-500 dark:text-gray-400 italic\">\n +{product.features.length - 4} more\n </span>\n )}\n </div>\n </div>\n )}\n\n {/* Visit Button */}\n <button className=\"w-full text-xs px-3 py-2 rounded-lg bg-black text-white hover:bg-gray-800 flex items-center justify-center gap-1 mt-auto\">\n Visit Offer\n <svg className=\"h-3 w-3\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14\" />\n </svg>\n </button>\n </AdMeshLinkTracker>\n ))}\n </div>\n\n {/* Powered by AdMesh branding */}\n <div className=\"flex items-center justify-center mt-6 pt-4 border-t border-gray-200/50 dark:border-gray-700/50\">\n <span className=\"flex items-center gap-1.5 text-xs text-gray-400 dark:text-gray-500\">\n <svg className=\"w-3 h-3 text-indigo-500\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\n <path fillRule=\"evenodd\" d=\"M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z\" clipRule=\"evenodd\" />\n </svg>\n <span className=\"font-medium\">Powered by</span>\n <span className=\"font-semibold text-indigo-600 dark:text-indigo-400\">AdMesh</span>\n </span>\n </div>\n </div>\n </div>\n );\n};\n\nAdMeshCompareTable.displayName = 'AdMeshCompareTable';\n","import React from 'react';\nimport classNames from 'classnames';\nimport type { AdMeshBadgeProps, BadgeType } from '../types/index';\n\n// Badge type to variant mapping\nconst badgeTypeVariants: Record<BadgeType, string> = {\n 'Top Match': 'primary',\n 'Free Tier': 'success',\n 'AI Powered': 'secondary',\n 'Popular': 'warning',\n 'New': 'primary',\n 'Trial Available': 'success'\n};\n\n// Badge type to icon mapping (using modern Unicode icons)\nconst badgeTypeIcons: Partial<Record<BadgeType, string>> = {\n 'Top Match': '★',\n 'Free Tier': '◆',\n 'AI Powered': '◉',\n 'Popular': '▲',\n 'New': '●',\n 'Trial Available': '◈'\n};\n\nexport const AdMeshBadge: React.FC<AdMeshBadgeProps> = ({\n type,\n variant,\n size = 'md',\n className\n}) => {\n const effectiveVariant = variant || badgeTypeVariants[type] || 'secondary';\n const icon = badgeTypeIcons[type];\n\n const badgeClasses = classNames(\n 'admesh-component',\n 'admesh-badge',\n `admesh-badge--${effectiveVariant}`,\n `admesh-badge--${size}`,\n className\n );\n\n return (\n <span className={badgeClasses}>\n {icon && <span className=\"admesh-badge__icon\">{icon}</span>}\n <span className=\"admesh-badge__text\">{type}</span>\n </span>\n );\n};\n\nAdMeshBadge.displayName = 'AdMeshBadge';\n","import { useEffect } from 'react';\n\n// CSS content as a string - this will be injected automatically\nconst ADMESH_STYLES = `\n/* AdMesh UI SDK Scoped Styles - Smart Recommendations Design */\n.admesh-component {\n --admesh-primary: #6366f1;\n --admesh-primary-hover: #4f46e5;\n --admesh-secondary: #8b5cf6;\n --admesh-accent: #06b6d4;\n --admesh-background: #ffffff;\n --admesh-surface: #ffffff;\n --admesh-border: #e2e8f0;\n --admesh-text: #0f172a;\n --admesh-text-muted: #64748b;\n --admesh-text-light: #94a3b8;\n --admesh-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);\n --admesh-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);\n --admesh-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);\n --admesh-shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);\n --admesh-radius: 0.75rem;\n --admesh-radius-sm: 0.375rem;\n --admesh-radius-lg: 1rem;\n --admesh-radius-xl: 1.5rem;\n}\n\n.admesh-component[data-admesh-theme=\"dark\"] {\n --admesh-background: #111827;\n --admesh-surface: #1f2937;\n --admesh-border: #374151;\n --admesh-text: #f9fafb;\n --admesh-text-muted: #9ca3af;\n}\n\n/* Layout Styles */\n.admesh-layout {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n color: var(--admesh-text);\n background-color: var(--admesh-background);\n border-radius: var(--admesh-radius);\n padding: 1.5rem;\n box-shadow: var(--admesh-shadow);\n border: 1px solid var(--admesh-border);\n}\n\n.admesh-layout__header {\n margin-bottom: 1.5rem;\n text-align: center;\n}\n\n.admesh-layout__title {\n font-size: 1.25rem;\n font-weight: 600;\n color: var(--admesh-text);\n margin-bottom: 0.5rem;\n}\n\n.admesh-layout__subtitle {\n font-size: 0.875rem;\n color: var(--admesh-text-muted);\n}\n\n.admesh-layout__cards-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n gap: 1rem;\n margin-bottom: 1.5rem;\n}\n\n.admesh-layout__more-indicator {\n text-align: center;\n padding: 1rem;\n color: var(--admesh-text-muted);\n font-size: 0.875rem;\n}\n\n.admesh-layout__empty {\n text-align: center;\n padding: 3rem 1rem;\n}\n\n.admesh-layout__empty-content h3 {\n font-size: 1.125rem;\n font-weight: 600;\n color: var(--admesh-text-muted);\n margin-bottom: 0.5rem;\n}\n\n.admesh-layout__empty-content p {\n font-size: 0.875rem;\n color: var(--admesh-text-muted);\n}\n\n/* Product Card Styles */\n.admesh-product-card {\n background-color: var(--admesh-surface);\n border: 1px solid var(--admesh-border);\n border-radius: var(--admesh-radius);\n padding: 1.5rem;\n transition: all 0.2s ease-in-out;\n position: relative;\n overflow: hidden;\n}\n\n.admesh-product-card:hover {\n box-shadow: var(--admesh-shadow-lg);\n transform: translateY(-2px);\n border-color: var(--admesh-primary);\n}\n\n.admesh-product-card__header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 1rem;\n}\n\n.admesh-product-card__title {\n font-size: 1.125rem;\n font-weight: 600;\n color: var(--admesh-text);\n margin-bottom: 0.5rem;\n line-height: 1.4;\n}\n\n.admesh-product-card__reason {\n font-size: 0.875rem;\n color: var(--admesh-text-muted);\n line-height: 1.5;\n margin-bottom: 1rem;\n}\n\n.admesh-product-card__match-score {\n margin-bottom: 1rem;\n}\n\n.admesh-product-card__match-score-label {\n display: flex;\n justify-content: space-between;\n align-items: center;\n font-size: 0.75rem;\n color: var(--admesh-text-muted);\n margin-bottom: 0.25rem;\n}\n\n.admesh-product-card__match-score-bar {\n width: 100%;\n height: 0.375rem;\n background-color: var(--admesh-border);\n border-radius: var(--admesh-radius-sm);\n overflow: hidden;\n}\n\n.admesh-product-card__match-score-fill {\n height: 100%;\n background: linear-gradient(90deg, var(--admesh-primary), #8b5cf6);\n border-radius: var(--admesh-radius-sm);\n transition: width 0.3s ease-in-out;\n}\n\n.admesh-product-card__badges {\n display: flex;\n flex-wrap: wrap;\n gap: 0.5rem;\n margin-bottom: 1rem;\n}\n\n.admesh-product-card__badge {\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n padding: 0.25rem 0.5rem;\n background-color: var(--admesh-primary);\n color: white;\n font-size: 0.75rem;\n font-weight: 500;\n border-radius: var(--admesh-radius-sm);\n}\n\n.admesh-product-card__badge--secondary {\n background-color: var(--admesh-secondary);\n}\n\n.admesh-product-card__keywords {\n display: flex;\n flex-wrap: wrap;\n gap: 0.25rem;\n margin-bottom: 1rem;\n}\n\n.admesh-product-card__keyword {\n padding: 0.125rem 0.375rem;\n background-color: var(--admesh-border);\n color: var(--admesh-text-muted);\n font-size: 0.75rem;\n border-radius: var(--admesh-radius-sm);\n}\n\n.admesh-product-card__footer {\n display: flex;\n justify-content: flex-end;\n margin-top: 1.5rem;\n}\n\n.admesh-product-card__button {\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.75rem 1.5rem;\n background: linear-gradient(90deg,