import type { CSSProperties } from 'react'; import { useRef } from 'react'; import { createPortal } from 'react-dom'; import { ChartNoAxesColumnIncreasing } from 'lucide-react'; import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, ResponsiveContainer } from 'recharts'; import { GlassCard } from '../shared/GlassCard'; import { AnalyticsIconInfo } from '../shared/AnalyticsIconInfo'; import { CardIcon } from '../shared/CardIcon'; import { CardEmptyState } from '../shared/CardEmptyState'; import { StatusPill } from '../shared/StatusPill'; import { TrendBadge } from '../shared/TrendBadge'; import { AI_BRAND_MAP } from '@/lib/brands'; interface EngineEntry { key: string; label: string; count: number; icon?: string; color?: string; } interface AIDiscoveryTrafficCardProps { totalVisits: number; totalWithEstimate?: number; trend: number; previousTotal?: number; trendLabel?: string; chartData: Array>; engineBreakdown: EngineEntry[]; statusText: string; windowLabel?: string; estimatedHumanReach?: number; claudeCrawler?: number; claudeUserLike?: number; gptCrawler?: number; gptUserLike?: number; extrapolationMethodology?: string; extrapolationMultiplier?: number; extrapolationHumanReachRatio?: number; className?: string; delay?: number; } function formatCount(n: number): string { if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(n >= 10_000_000 ? 0 : 1)}M`; if (n >= 10_000) return `${(n / 1000).toFixed(0)}k`; if (n >= 1000) return `${(n / 1000).toFixed(1)}k`; return `${Math.round(n)}`; } const AI_TOOLTIP_KEYS = ['gpt', 'claude', 'google', 'perplexity', 'bing', 'meta', 'bytedance', 'apple', 'amazon', 'xai', 'deepseek', 'mistral', 'cohere'] as const; const TOOLTIP_PANEL: CSSProperties = { boxSizing: 'border-box', width: 118, background: 'rgba(255, 255, 255, 0.02)', border: '0.75px solid rgba(255, 255, 255, 0.1)', backdropFilter: 'blur(5px)', WebkitBackdropFilter: 'blur(5px)', borderRadius: 8, padding: '13px 9px 10px', pointerEvents: 'none', }; const TOTAL_STYLE: CSSProperties = { margin: 0, fontSize: 12, lineHeight: '13px', letterSpacing: '-0.24px', color: '#ffffff', }; const DIVIDER_STYLE: CSSProperties = { height: 1, margin: '7px -9px 0', background: 'rgba(255, 255, 255, 0.1)', }; const ENGINE_ROW_STYLE: CSSProperties = { display: 'flex', alignItems: 'center', gap: 6, marginTop: 6, }; const ENGINE_LABEL_STYLE: CSSProperties = { flex: 1, fontSize: 9.98, lineHeight: 1, letterSpacing: '-0.1996px', color: '#ffffff', }; const ENGINE_VALUE_STYLE: CSSProperties = { fontSize: 8.317, lineHeight: 1, letterSpacing: '-0.1663px', color: 'rgba(255, 255, 255, 0.5)', textAlign: 'right', }; function ChartTooltip(props: any) { const { active, payload, coordinate, containerRef } = props; if (!active || !payload?.length || !coordinate) return null; const rect = containerRef?.current?.getBoundingClientRect(); const absX = (rect?.left ?? 0) + (coordinate.x ?? 0); const absY = (rect?.top ?? 0) + (coordinate.y ?? 0); const dataPoint = payload[0]?.payload; const total = dataPoint?.total ?? 0; const engines = AI_TOOLTIP_KEYS.filter((key) => (dataPoint?.[key] || 0) > 0) .map((key) => ({ key, value: dataPoint[key] as number, label: AI_BRAND_MAP[key]?.label || key, icon: AI_BRAND_MAP[key]?.icon, })) .sort((a, b) => b.value - a.value); if (typeof document === 'undefined') return null; return createPortal(

{total} visits

{engines.length > 0 &&
} {engines.map((engine) => (
{engine.icon ? ( ) : (
)} {engine.label} {engine.value}
))}
, document.body, ); } export function AIDiscoveryTrafficCard({ totalVisits, trend, chartData, engineBreakdown, statusText, className = '', delay = 0, }: AIDiscoveryTrafficCardProps) { const chartContainerRef = useRef(null); const visibleEngineBreakdown = engineBreakdown.slice(0, 4); const chartMax = chartData.reduce((m, p) => Math.max(m, Number(p.total) || 0), 0); const isChartEmpty = chartData.length === 0 || chartMax === 0; const isLowTraffic = chartMax > 0 && chartMax <= 3; const isSinglePoint = chartData.length === 1; const displayData = isSinglePoint ? [{ ...chartData[0], total: 0, name: '' }, chartData[0]] : chartData; const xTickInterval = displayData.length <= 8 ? 0 : Math.max(0, Math.floor(displayData.length / 7) - 1); return (
Measured AI crawler and AI-assistant visibility for your site in the selected window.

} >

AI Discovery Traffic

{formatCount(totalVisits)} visits {trend !== 0 ? : null}
{isChartEmpty ? ( ) : ( } cursor={{ stroke: 'rgba(255,255,255,0.1)' }} wrapperStyle={{ outline: 'none' }} /> )}
{visibleEngineBreakdown.length > 0 ? (
{visibleEngineBreakdown.map((engine) => (
{engine.icon ? ( ) : null} {engine.label} {engine.count}
))}
) : null}
); }