{
  "name": "SEO-FLOW",
  "nodes": [
    {
      "parameters": {},
      "id": "fb20a66e-e8c2-4cec-97fc-514a1d64ecc7",
      "name": "Run Analysis",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        384,
        1088
      ],
      "alwaysOutputData": false,
      "disabled": true
    },
    {
      "parameters": {
        "jsCode": "// Configuration using ENVIRONMENT VARIABLES for security\n// All secrets are read from process.env so they're not in the workflow JSON\nconst CONFIG = {\n  // GSC Config\n  gsc: {\n    site: $env.GSC_SITE || 'sc-domain:bestwaygujarat.com',\n    clientId: $env.GSC_CLIENT_ID,\n    clientSecret: $env.GSC_CLIENT_SECRET,\n    refreshToken: $env.GSC_REFRESH_TOKEN\n  },\n  \n  // SearchAPI Keys (rotation)\n  searchapi: {\n    keys: ($env.SEARCHAPI_KEYS || '').split(',').filter(k => k.trim()),\n    currentKeyIndex: 0,\n    baseUrl: 'https://www.searchapi.io/api/v1/search'\n  },\n  \n  // OpenRouter Config\n  openrouter: {\n    apiKey: $env.OPENROUTER_API_KEY,\n    model: $env.OPENROUTER_MODEL || 'xiaomi/mimo-v2-flash:free',\n    endpoint: 'https://openrouter.ai/api/v1/chat/completions'\n  }\n};\n\n// Calculate date ranges with edge cases\nconst now = new Date();\nconst endDate = new Date(now);\nendDate.setDate(endDate.getDate() - 2); // GSC data lag\nconst startDate = new Date(endDate);\nstartDate.setMonth(startDate.getMonth() - 6); // 6 months\n// Previous period for comparison\nconst prevEndDate = new Date(startDate);\nprevEndDate.setDate(prevEndDate.getDate() - 1);\nconst prevStartDate = new Date(prevEndDate);\nprevStartDate.setDate(prevStartDate.getDate() - 6);\n// 6-MONTH TREND RANGE\nconst trendEnd = new Date(endDate);\nconst trendStart = new Date(startDate);\nconst formatDate = (d) => d.toISOString().split('T')[0];\n// Edge case: Validate dates\nconst validateDate = (dateStr) => {\n  const d = new Date(dateStr);\n  return !isNaN(d.getTime()) ? dateStr : formatDate(new Date());\n};\n\n// Validate required env vars\nconst missing = [];\nif (!CONFIG.gsc.clientId) missing.push('GSC_CLIENT_ID');\nif (!CONFIG.gsc.clientSecret) missing.push('GSC_CLIENT_SECRET');\nif (!CONFIG.gsc.refreshToken) missing.push('GSC_REFRESH_TOKEN');\nif (!CONFIG.openrouter.apiKey) missing.push('OPENROUTER_API_KEY');\nif (CONFIG.searchapi.keys.length === 0) missing.push('SEARCHAPI_KEYS');\n\nif (missing.length > 0) {\n  throw new Error(`Missing required environment variables: ${missing.join(', ')}`);\n}\n\nreturn {\n  json: {\n    config: CONFIG,\n    dates: {\n      current: {\n        start: validateDate(formatDate(startDate)),\n        end: validateDate(formatDate(endDate))\n      },\n      previous: {\n        start: validateDate(formatDate(prevStartDate)),\n        end: validateDate(formatDate(prevEndDate))\n      },\n      trend: {\n        start: validateDate(formatDate(trendStart)),\n        end: validateDate(formatDate(trendEnd))\n      }\n    },\n    searchapiKey: CONFIG.searchapi.keys[0],\n    searchapiAllKeys: CONFIG.searchapi.keys, // All keys available for rotation\n    generatedAt: now.toISOString()\n  }\n};"
      },
      "id": "f9c3ee3a-d832-4da2-bd51-60bdb7ae9db9",
      "name": "Config + API Keys",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        704,
        1328
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://oauth2.googleapis.com/token",
        "sendBody": true,
        "contentType": "form-urlencoded",
        "bodyParameters": {
          "parameters": [
            {
              "name": "client_id",
              "value": "={{ $json.config.gsc.clientId }}"
            },
            {
              "name": "client_secret",
              "value": "={{ $json.config.gsc.clientSecret }}"
            },
            {
              "name": "refresh_token",
              "value": "={{ $json.config.gsc.refreshToken }}"
            },
            {
              "name": "grant_type",
              "value": "refresh_token"
            }
          ]
        },
        "options": {}
      },
      "id": "9d7d7f32-7f2b-421d-869b-907fcc2f9615",
      "name": "GSC Token",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        960,
        1328
      ]
    },
    {
      "parameters": {
        "jsCode": "// Merge token with config, handle edge case of token failure\nconst config = $('Config + API Keys').first().json;\nconst tokenResponse = $json;\n\n// Edge case: Token refresh failed\nif (!tokenResponse.access_token) {\n  throw new Error('GSC Token refresh failed. Check credentials.');\n}\n\nreturn {\n  json: {\n    ...config,\n    gscToken: tokenResponse.access_token\n  }\n};"
      },
      "id": "019f857a-388b-48ad-a442-f6b1eb4e8167",
      "name": "Merge Token",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1200,
        1328
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://www.googleapis.com/webmasters/v3/sites/{{ encodeURIComponent($json.config.gsc.site) }}/searchAnalytics/query",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $json.gscToken }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"startDate\": \"{{ $json.dates.current.start }}\",\n  \"endDate\": \"{{ $json.dates.current.end }}\",\n  \"dimensions\": [\"query\"],\n  \"rowLimit\": 500\n}",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "94947b99-76eb-469a-85a1-77568d1d4a51",
      "name": "GSC: Keywords",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1504,
        944
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://www.googleapis.com/webmasters/v3/sites/{{ encodeURIComponent($('Merge Token').item.json.config.gsc.site) }}/searchAnalytics/query",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $('Merge Token').item.json.gscToken }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"startDate\": \"{{ $('Merge Token').item.json.dates.current.start }}\",\n  \"endDate\": \"{{ $('Merge Token').item.json.dates.current.end }}\",\n  \"dimensions\": [\"page\"],\n  \"rowLimit\": 200\n}",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "e53ff729-8fd7-4ea3-b500-e3fdde6b2ef8",
      "name": "GSC: Pages",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1504,
        1680
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://www.googleapis.com/webmasters/v3/sites/{{ encodeURIComponent($('Merge Token').item.json.config.gsc.site) }}/searchAnalytics/query",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $('Merge Token').item.json.gscToken }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"startDate\": \"{{ $('Merge Token').item.json.dates.current.start }}\",\n  \"endDate\": \"{{ $('Merge Token').item.json.dates.current.end }}\",\n  \"dimensions\": [\"device\"]\n}",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "e9889f8b-a113-43e5-801a-10bda0145b1c",
      "name": "GSC: Devices",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1504,
        1152
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://www.googleapis.com/webmasters/v3/sites/{{ encodeURIComponent($('Merge Token').item.json.config.gsc.site) }}/searchAnalytics/query",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $('Merge Token').item.json.gscToken }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"startDate\": \"{{ $('Merge Token').item.json.dates.current.start }}\",\n  \"endDate\": \"{{ $('Merge Token').item.json.dates.current.end }}\",\n  \"dimensions\": [\"country\"],\n  \"rowLimit\": 50\n}",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "07334e4b-8afc-4fe6-807d-cb64a47cefad",
      "name": "GSC: Countries",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1504,
        1328
      ]
    },
    {
      "parameters": {
        "url": "=https://www.searchapi.io/api/v1/search?engine=google_trends&q=swimming+pool&data_type=TIMESERIES&time=today+12-m&geo=IN&api_key={{ $('Merge Token').item.json.searchapiKey }}",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "9b58af05-e12a-4b97-a22c-873f507cefbb",
      "name": "Trends: Niche Interest",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1504,
        2048
      ]
    },
    {
      "parameters": {
        "url": "=https://www.searchapi.io/api/v1/search?engine=google_trends&q=swimming+pool&data_type=RELATED_QUERIES&geo=IN&api_key={{ $('Merge Token').item.json.searchapiKey }}",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "b7f1f384-35bb-4df6-bfef-4bcc6118d441",
      "name": "Trends: Related Keywords",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1504,
        2208
      ]
    },
    {
      "parameters": {
        "url": "=https://www.searchapi.io/api/v1/search?engine=google_rank_tracking&q=bestway+pool+india&gl=in&num=10&api_key={{ $('Merge Token').item.json.searchapiKey }}",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "fe923fa7-fba4-4fa0-87ef-a6baa02d0e3a",
      "name": "SERP: Competitors",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1504,
        752
      ]
    },
    {
      "parameters": {
        "jsCode": "\nconst data = $json;\nlet config = {};\ntry {\n  config = $('Merge Token').first().json.config;\n} catch (e) {\n  // Ignore error if specific node ref fails\n}\n\n// Ensure we output something even if logic fails\nif (!data) {\n  return { json: { source: 'gsc_keywords', hasData: false, error: 'No input data', stats: { total: 0, clicks: 0, impressions: 0 } } };\n}\n\nif (data.error || !data.rows) {\n  return {\n    json: {\n      source: 'gsc_keywords',\n      hasData: false,\n      error: data?.error?.message || 'No rows returned',\n      keywords: [],\n      stats: { total: 0, clicks: 0, impressions: 0 },\n      config\n    }\n  };\n}\n\nconst keywords = data.rows || [];\n\n// Calculate stats\nconst totalClicks = keywords.reduce((sum, r) => sum + (r.clicks || 0), 0);\nconst totalImpressions = keywords.reduce((sum, r) => sum + (r.impressions || 0), 0);\nconst avgCTR = totalImpressions > 0 ? (totalClicks / totalImpressions * 100).toFixed(2) : 0;\nconst avgPosition = keywords.length > 0\n  ? (keywords.reduce((sum, r) => sum + (r.position || 0), 0) / keywords.length).toFixed(1)\n  : 0;\n\n// Top keywords\nconst topKeywords = keywords.slice(0, 50).map((r, i) => ({\n  rank: i + 1,\n  keyword: r.keys?.[0] || 'unknown',\n  clicks: r.clicks || 0,\n  impressions: r.impressions || 0,\n  ctr: ((r.ctr || 0) * 100).toFixed(2) + '%',\n  position: (r.position || 0).toFixed(1)\n}));\n\n// Quick wins\nconst quickWins = keywords\n  .filter(r => (r.impressions || 0) > 20 && (r.ctr || 0) < 0.03 && (r.position || 0) >= 5 && (r.position || 0) <= 20)\n  .slice(0, 10)\n  .map(r => ({\n    keyword: r.keys?.[0] || 'unknown',\n    impressions: r.impressions || 0,\n    ctr: ((r.ctr || 0) * 100).toFixed(2) + '%',\n    position: (r.position || 0).toFixed(1),\n    opportunity: 'Improve title/meta for better CTR'\n  }));\n\nreturn {\n  json: {\n    source: 'gsc_keywords',\n    hasData: true,\n    stats: {\n      total: keywords.length,\n      clicks: totalClicks,\n      impressions: totalImpressions,\n      avgCTR: avgCTR + '%',\n      avgPosition\n    },\n    topKeywords,\n    quickWins,\n    config\n  }\n};\n"
      },
      "id": "f47bd84b-fdcc-4ce6-a45b-32bcd925a991",
      "name": "Process Keywords",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2352,
        944
      ]
    },
    {
      "parameters": {
        "jsCode": "// Process GSC Pages with edge cases\nconst data = $json;\n\nif (!data || data.error || !data.rows) {\n  return {\n    json: {\n      source: 'gsc_pages',\n      hasData: false,\n      error: data?.error?.message || 'No page data',\n      pages: []\n    }\n  };\n}\n\nconst pages = (data.rows || []).slice(0, 15).map((r, i) => ({\n  rank: i + 1,\n  url: r.keys?.[0] || 'unknown',\n  clicks: r.clicks || 0,\n  impressions: r.impressions || 0,\n  ctr: ((r.ctr || 0) * 100).toFixed(2) + '%',\n  position: (r.position || 0).toFixed(1)\n}));\n\nreturn {\n  json: {\n    source: 'gsc_pages',\n    hasData: true,\n    pages\n  }\n};"
      },
      "id": "4f40f556-1741-4669-842c-9321f58b1680",
      "name": "Process Pages",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1824,
        1680
      ]
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineAll",
        "options": {}
      },
      "id": "411be157-b9a6-4fe2-961f-e41ee8435411",
      "name": "Merge Audience",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        1824,
        1232
      ]
    },
    {
      "parameters": {
        "jsCode": "// Process Devices + Countries\nconst items = $input.all();\n\nlet devicesRows = [], countriesRows = [];\n\n// Identify inputs by checking first row's key pattern\nfor (const item of items) {\n  const rows = item.json.rows || [];\n  if (rows.length > 0) {\n    const firstKey = (rows[0].keys?.[0] || '').toUpperCase();\n    if (['MOBILE', 'DESKTOP', 'TABLET'].includes(firstKey)) {\n       devicesRows = rows;\n    } else {\n       countriesRows = rows;\n    }\n  }\n}\n\n// Format devices with position data\nconst devices = devicesRows.map(r => ({\n  device: r.keys?.[0] || 'Unknown',\n  clicks: r.clicks || 0,\n  impressions: r.impressions || 0,\n  ctr: ((r.ctr || 0) * 100).toFixed(2) + '%',\n  position: (r.position || 0).toFixed(1)  // ADD THIS\n}));\n\n// Format countries\nconst countries = countriesRows.slice(0, 15).map(r => ({\n  country: r.keys?.[0] || 'Unknown',\n  clicks: r.clicks || 0,\n  impressions: r.impressions || 0,\n  ctr: ((r.ctr || 0) * 100).toFixed(2) + '%',\n  position: (r.position || 0).toFixed(1)  // ADD THIS\n}));\n\nreturn {\n  json: {\n    source: 'audience',\n    hasData: devices.length > 0 || countries.length > 0,\n    devices,\n    countries\n  }\n};"
      },
      "id": "7dd90389-a9d4-4d7c-a6f6-5d1402670117",
      "name": "Process Audience",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2352,
        1232
      ]
    },
    {
      "parameters": {
        "jsCode": "// Process 28-day trends with edge cases\nconst data = $json;\n\nif (!data || data.error || !data.rows) {\n  return {\n    json: {\n      source: 'gsc_trends',\n      hasData: false,\n      trend: 'unknown',\n      dailyData: []\n    }\n  };\n}\n\nconst dailyData = (data.rows || []).map(r => ({\n  date: r.keys?.[0] || 'unknown',\n  clicks: r.clicks || 0,\n  impressions: r.impressions || 0\n}));\n\n// Calculate trend direction\nconst recent = dailyData.slice(-7);\nconst older = dailyData.slice(0, 7);\nconst recentAvg = recent.reduce((s, d) => s + d.clicks, 0) / (recent.length || 1);\nconst olderAvg = older.reduce((s, d) => s + d.clicks, 0) / (older.length || 1);\nconst trend = recentAvg > olderAvg * 1.1 ? 'up' : recentAvg < olderAvg * 0.9 ? 'down' : 'stable';\n\nreturn {\n  json: {\n    source: 'gsc_trends',\n    hasData: true,\n    trend,\n    trendText: trend.toUpperCase(),\n    dailyData\n  }\n};"
      },
      "id": "23dbd959-eb40-458e-8092-aeb20705e15e",
      "name": "Process Trends",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1824,
        1872
      ]
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineAll",
        "options": {}
      },
      "id": "815a4807-00c9-4402-b9dc-a73654e9632f",
      "name": "Merge SearchAPI",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        1824,
        2128
      ]
    },
    {
      "parameters": {
        "jsCode": "// Process SearchAPI data\nconst items = $input.all();\n\nlet nicheInterest = [], relatedQueries = [], competitors = [];\nlet error = null;\n\nfor (const item of items) {\n  const json = item.json;\n  \n  if (json.error) {\n    error = json.error;\n    // Don't just continue, try to extract whatever is possible or set specific flags\n    continue;\n  }\n\n  if (json.interest_over_time) {\n    nicheInterest = json.interest_over_time.timeline_data || [];\n  }\n  else if (json.related_queries) {\n    // Robust check for various SearchAPI return structures\n    relatedQueries = json.related_queries.rising || json.related_queries.top || [];\n  }\n  else if (json.organic_results) {\n    competitors = json.organic_results || [];\n  }\n}\n\nreturn {\n  json: {\n    source: 'searchapi',\n    hasData: nicheInterest.length > 0 || relatedQueries.length > 0 || competitors.length > 0,\n    searchApiError: error,\n    nicheInterest: nicheInterest.slice(-12).map(d => ({\n      date: d.date || 'unknown',\n      value: d.values?.[0]?.value || 0\n    })),\n    risingKeywords: relatedQueries.slice(0, 10).map(q => ({\n      keyword: q.query || 'unknown',\n      growth: q.value || q.extracted_value || 'N/A'\n    })),\n    competitors: competitors.slice(0, 5).map((r, i) => ({\n      position: r.position || i + 1,\n      title: r.title || 'unknown',\n      domain: r.domain || r.link || 'unknown'\n    }))\n  }\n};"
      },
      "id": "1f72fa00-769b-4be0-8dec-f0e01fc2e1fb",
      "name": "Process SearchAPI",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2096,
        2128
      ]
    },
    {
      "parameters": {
        "jsCode": "// Build comprehensive AI prompt with DYNAMIC site detection\nconst items = $input.all();\n\n// Extract data from the merged array\nlet keywordData = {}, pageData = {}, audienceData = {}, trendData = {}, searchapiData = {}, config = {};\nlet appearanceData = {}, sitemapData = {};\n\nfor (const item of items) {\n  const d = item.json;\n  const src = d.source || '';\n  \n  if (src === 'gsc_keywords' || d.topKeywords) { keywordData = d; config = d.config || config; }\n  if (src === 'gsc_pages' || d.pages) { pageData = d; }\n  if (src === 'audience' || d.devices || d.countries) { audienceData = d; }\n  if (src === 'gsc_trends' || d.dailyData) { trendData = d; }\n  if (src === 'searchapi' || d.risingKeywords || d.competitors) { searchapiData = d; }\n  if (src === 'appearance' || d.features) { appearanceData = d; }\n  if (src === 'sitemaps' || d.sitemaps) { sitemapData = d; }\n}\n\n// Fallbacks for critical data\nif (!keywordData.stats && pageData.stats) { keywordData.stats = pageData.stats; }\nif ((!keywordData.topKeywords || keywordData.topKeywords.length === 0) && pageData.topKeywords) { keywordData.topKeywords = pageData.topKeywords; }\nif ((!keywordData.quickWins || keywordData.quickWins.length === 0) && pageData.quickWins) { keywordData.quickWins = pageData.quickWins; }\n\n// Extract site name dynamically from config\nconst siteName = (config.gsc?.site || 'your-site.com')\n  .replace('sc-domain:', '')  // Remove GSC prefix\n  .replace('https://', '')     // Clean URL\n  .replace('http://', '')\n  .replace('www.', '');\n\n// Detect primary market from countries data\nconst topCountry = audienceData.countries?.[0]?.country || 'unknown';\nconst marketMap = {\n  'ind': 'India',\n  'usa': 'United States', \n  'gbr': 'United Kingdom',\n  'can': 'Canada',\n  'aus': 'Australia'\n};\nconst primaryMarket = marketMap[topCountry.toLowerCase()] || topCountry.toUpperCase();\n\n// Infer niche from top keywords (basic classification)\nconst topKeywordsText = (keywordData.topKeywords || []).slice(0, 10).map(k => k.keyword).join(' ').toLowerCase();\nlet nicheGuess = 'General Business';\n\nif (topKeywordsText.includes('pool') || topKeywordsText.includes('swimming')) {\n  nicheGuess = 'Swimming Pool Equipment';\n} else if (topKeywordsText.includes('seo') || topKeywordsText.includes('marketing')) {\n  nicheGuess = 'Digital Marketing / SEO';\n} else if (topKeywordsText.includes('ecommerce') || topKeywordsText.includes('shop')) {\n  nicheGuess = 'E-commerce';\n} else if (topKeywordsText.includes('saas') || topKeywordsText.includes('software')) {\n  nicheGuess = 'SaaS / Software';\n}\n\n// Build AI prompt with dynamic data\nconst prompt = `You are an expert SEO analyst. Analyze this Google Search Console data and provide actionable insights.\n\n## WEBSITE DATA\nSite: ${siteName}\nNiche: ${nicheGuess} (B2C/B2B - auto-detected)\nPrimary Market: ${primaryMarket}\n\n## PERFORMANCE SUMMARY\n- Total Keywords: ${keywordData.stats?.total || 0}\n- Total Clicks: ${keywordData.stats?.clicks || 0}\n- Total Impressions: ${keywordData.stats?.impressions || 0}\n- Average CTR: ${keywordData.stats?.avgCTR || '0%'}\n- Average Position: ${keywordData.stats?.avgPosition || 'N/A'}\n- Trend: ${trendData.trend || 'unknown'}\n\n## TOP 10 KEYWORDS\n${(keywordData.topKeywords || []).slice(0, 10).map(k => \n  `- \"${k.keyword}\": ${k.clicks} clicks, ${k.impressions} impr, pos ${k.position}`\n).join('\\n')}\n\n## TOP PAGES\n${(pageData.pages || []).slice(0, 5).map(p => \n  `- ${p.url}: ${p.clicks} clicks`\n).join('\\n')}\n\n## DEVICE BREAKDOWN\n${(audienceData.devices || []).map(d => \n  `- ${d.device}: ${d.clicks} clicks (${d.ctr} CTR)`\n).join('\\n')}\n\n## TOP COUNTRIES\n${(audienceData.countries || []).slice(0, 5).map(c => \n  `- ${c.country}: ${c.clicks} clicks`\n).join('\\n')}\n\n## QUICK WIN OPPORTUNITIES\n${(keywordData.quickWins || []).slice(0, 5).map(q => \n  `- \"${q.keyword}\": ${q.impressions} impr, ${q.ctr} CTR, pos ${q.position}`\n).join('\\n')}\n\n## RISING KEYWORDS IN NICHE (Google Trends)\n${(searchapiData.risingKeywords || []).slice(0, 5).map(r => \n  `- \"${r.keyword}\": ${r.growth}`\n).join('\\n')}\n\n## TOP COMPETITORS\n${(searchapiData.competitors || []).map(c => \n  `${c.position}. ${c.domain}`\n).join('\\n')}\n\n---\n\n## ANALYSIS REQUIRED:\n1. **Executive Summary** (3 sentences max)\n2. **Keyword Clusters** - Group the top keywords into Brand/Product/Informational and explain WHY each performs as it does\n3. **Quick Wins** - Pick the top 3 opportunities with specific actions and expected impact\n4. **Competitive Gap** - What are competitors doing that we're not?\n5. **3-5 Specific Recommendations** - Actionable, prioritized by impact\n\nRespond in JSON format:\n{\n  \"executive_summary\": \"...\",\n  \"keyword_clusters\": { \"brand\": [...], \"product\": [...], \"informational\": [...] },\n  \"quick_wins\": [{ \"keyword\": \"...\", \"action\": \"...\", \"expected_impact\": \"...\" }],\n  \"competitive_gap\": \"...\",\n  \"recommendations\": [{ \"priority\": 1, \"action\": \"...\", \"impact\": \"...\" }]\n}`;\n\nreturn {\n  json: {\n    prompt,\n    rawData: { keywordData, pageData, audienceData, trendData, searchapiData, appearanceData, sitemapData },\n    config,\n    detectedSite: siteName,\n    detectedNiche: nicheGuess,\n    detectedMarket: primaryMarket\n  }\n};"
      },
      "id": "0e849981-17f7-4f1c-9d59-76531e48d0a1",
      "name": "Build AI Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2992,
        1376
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://openrouter.ai/api/v1/chat/completions",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "==Bearer {{ $('Config + API Keys').first().json.config.openrouter.apiKey }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"model\": \"xiaomi/mimo-v2-flash:free\",\n  \"messages\": [\n    {\n      \"role\": \"user\",\n      \"content\": {{ JSON.stringify($json.prompt) }}\n    }\n  ]\n}",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "eb73d2a2-6a4f-414a-aef5-f536d4c2fc80",
      "name": "OpenRouter AI",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3232,
        1376
      ]
    },
    {
      "parameters": {
        "jsCode": "// Parse AI response with edge cases\nconst aiResponse = $json;\nconst rawData = $('Build AI Prompt').first().json.rawData;\n\n// Edge case: AI call failed\nlet aiInsights = null;\nlet parseError = null;\n\nif (aiResponse.choices && aiResponse.choices[0]?.message?.content) {\n  const content = aiResponse.choices[0].message.content;\n  \n  // Try to extract JSON from response\n  try {\n    // Find JSON in response\n    const jsonMatch = content.match(/\\{[\\s\\S]*\\}/);\n    if (jsonMatch) {\n      aiInsights = JSON.parse(jsonMatch[0]);\n    } else {\n      // Use raw text as summary\n      aiInsights = {\n        executive_summary: content.substring(0, 500),\n        recommendations: [{ priority: 1, action: 'Review AI response manually', impact: 'Unknown' }]\n      };\n    }\n  } catch (e) {\n    parseError = e.message;\n    aiInsights = {\n      executive_summary: content.substring(0, 500),\n      recommendations: []\n    };\n  }\n} else {\n  aiInsights = {\n    executive_summary: 'AI analysis unavailable. Please review data manually.',\n    recommendations: []\n  };\n  parseError = aiResponse.error?.message || 'No AI response';\n}\n\nreturn {\n  json: {\n    aiInsights,\n    parseError,\n    rawData\n  }\n};\n"
      },
      "id": "eaafd679-2f28-4a65-a758-bd76734370f3",
      "name": "Parse AI Output",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3536,
        1376
      ]
    },
    {
      "parameters": {
        "jsCode": "// Generate comprehensive markdown report - FIXED\nconst { aiInsights, rawData, parseError } = $json;\n\n// Use rawData but also fallback to look for data in different places\nlet kd = rawData?.keywordData || {};\nlet pd = rawData?.pageData || {};\nconst ad = rawData?.audienceData || {};\nconst td = rawData?.trendData || {};\nconst sd = rawData?.searchapiData || {};\n\n// If keyword data is empty but page data has topKeywords, use pageData for keywords\nif ((!kd.topKeywords || kd.topKeywords.length === 0) && pd.topKeywords) {\n  kd = { ...kd, topKeywords: pd.topKeywords, quickWins: pd.quickWins, stats: pd.stats };\n}\n\nconst now = new Date().toISOString();\n\nconst report = `# SEO Intelligence Report\n**Site:** bestwaygujarat.com  \n**Generated:** ${now}  \n**Trend:** ${td.trendText || 'STABLE'}\n\n---\n\n## Executive Summary\n${aiInsights?.executive_summary || 'Analysis in progress...'}\n\n---\n\n## Performance Dashboard\n\n| Metric | Value |\n|--------|-------|\n| Total Keywords | ${kd.stats?.total || 0} |\n| Total Clicks | ${kd.stats?.clicks || 0} |\n| Total Impressions | ${kd.stats?.impressions || 0} |\n| Average CTR | ${kd.stats?.avgCTR || '0%'} |\n| Average Position | ${kd.stats?.avgPosition || 'N/A'} |\n\n---\n\n## Top Keywords\n\n| Rank | Keyword | Clicks | Impressions | CTR | Position |\n|------|---------|--------|-------------|-----|----------|\n${(kd.topKeywords || []).slice(0, 15).map(k => \n  `| ${k.rank} | ${k.keyword.substring(0, 40)} | ${k.clicks} | ${k.impressions} | ${k.ctr} | ${k.position} |`\n).join('\\n')}\n\n---\n\n## Top Pages\n\n| Rank | URL | Clicks | Position |\n|------|-----|--------|----------|\n${(pd.pages || []).slice(0, 10).map(p => \n  `| ${p.rank} | ${p.url.substring(0, 50)}... | ${p.clicks} | ${p.position} |`\n).join('\\n')}\n\n---\n\n## Device Breakdown\n\n| Device | Clicks | CTR |\n|--------|--------|-----|\n${(ad.devices || []).map(d => \n  `| ${d.device} | ${d.clicks} | ${d.ctr} |`\n).join('\\n')}\n\n---\n\n## Top Countries\n\n| Country | Clicks |\n|---------|--------|\n${(ad.countries || []).slice(0, 5).map(c => \n  `| ${c.country} | ${c.clicks} |`\n).join('\\n')}\n\n---\n\n## Quick Win Opportunities\n\n${(aiInsights?.quick_wins || kd.quickWins || []).slice(0, 5).map((q, i) => \n  `**${i + 1}. ${q.keyword || 'Opportunity'}**  \nAction: ${q.action || q.opportunity}  \nExpected Impact: ${q.expected_impact || 'Improved CTR'}\n`\n).join('\\n')}\n\n---\n\n## Rising Keywords in Niche (Google Trends)\n\n${(sd.risingKeywords || []).slice(0, 5).map(r => \n  `- **${r.keyword}** - Growth: ${r.growth}`\n).join('\\n') || 'No trending data available'}\n\n---\n\n## Competitive Landscape\n\n${aiInsights?.competitive_gap || 'See competitor ranking below'}\n\n| Position | Competitor |\n|----------|------------|\n${(sd.competitors || []).map(c => \n  `| ${c.position} | ${c.domain} |`\n).join('\\n') || '| - | No data |'}\n\n---\n\n## AI Recommendations\n\n${(aiInsights?.recommendations || []).map((r, i) => \n  `### Priority ${r.priority || i + 1}\n**Action:** ${r.action}  \n**Impact:** ${r.impact}\n`\n).join('\\n') || 'Review data manually for recommendations.'}\n\n---\n\n*Report generated by n8n SEO Intelligence Workflow*\n`;\n\nreturn {\n  json: {\n    report,\n    reportFormat: 'markdown',\n    generatedAt: now,\n    rawData,\n    aiInsights,\n    dataQuality: {\n      hasKeywords: (kd.topKeywords?.length || 0) > 0,\n      hasPages: (pd.pages?.length || 0) > 0,\n      hasDevices: (ad.devices?.length || 0) > 0,\n      hasTrends: (td.dailyData?.length || 0) > 0,\n      hasSearchAPI: (sd.risingKeywords?.length || 0) > 0,\n      hasAI: !!aiInsights?.executive_summary,\n      parseError\n    }\n  }\n};\n"
      },
      "id": "34d154a1-6c65-4cb2-9bed-e3b7e44796a2",
      "name": "Generate Report",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3840,
        1376
      ]
    },
    {
      "parameters": {
        "url": "=https://www.googleapis.com/webmasters/v3/sites/{{ encodeURIComponent($('Merge Token').item.json.config.gsc.site) }}/sitemaps",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $('Merge Token').item.json.gscToken }}"
            }
          ]
        },
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "241e74e7-0655-4273-99ff-682c6f067ac1",
      "name": "GSC: Sitemaps",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1504,
        1520
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://www.googleapis.com/webmasters/v3/sites/{{ encodeURIComponent($('Merge Token').item.json.config.gsc.site) }}/searchAnalytics/query",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $('Merge Token').item.json.gscToken }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\"startDate\": \"{{ $('Merge Token').item.json.dates.current.start }}\", \"endDate\": \"{{ $('Merge Token').item.json.dates.current.end }}\", \"dimensions\": [\"searchAppearance\"]}",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "9f683448-6045-4e09-9718-fd3bcaf17e5d",
      "name": "GSC: Search Appearance",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1504,
        560
      ]
    },
    {
      "parameters": {
        "jsCode": "\nconst data = $json;\nif (!data || data.error) {\n  return { json: { source: 'sitemaps', hasData: false, sitemaps: [], warning: 'Could not fetch sitemaps' } };\n}\n\nconst sitemaps = (data.sitemap || []).map(s => ({\n  path: s.path || '',\n  lastDownloaded: s.lastDownloaded || 'Never',\n  webPages: s.contents?.find(c => c.type === 'web')?.submitted || '0',\n  indexed: s.contents?.find(c => c.type === 'web')?.indexed || '0',\n  status: parseInt(s.errors || 0) > 0 ? 'ERROR' : 'OK'\n}));\n\nconst totalSubmitted = sitemaps.reduce((s, sm) => s + parseInt(sm.webPages || 0), 0);\nconst totalIndexed = sitemaps.reduce((s, sm) => s + parseInt(sm.indexed || 0), 0);\n\nreturn { json: { source: 'sitemaps', hasData: true, sitemaps, totalSubmitted, totalIndexed } };\n"
      },
      "id": "14dee3d8-1156-4446-8d1d-2ed604875fc7",
      "name": "Process Sitemaps",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1824,
        1520
      ]
    },
    {
      "parameters": {
        "jsCode": "\nconst data = $json;\nif (!data || data.error || !data.rows) {\n  return { json: { source: 'appearance', hasData: false, features: [] } };\n}\n\nconst features = (data.rows || []).map(r => ({\n  type: r.keys?.[0] || '',\n  clicks: r.clicks || 0,\n  impressions: r.impressions || 0,\n  ctr: ((r.ctr || 0) * 100).toFixed(2) + '%'\n})).sort((a, b) => b.clicks - a.clicks);\n\nreturn { json: { source: 'appearance', hasData: true, features } };\n"
      },
      "id": "b1391310-be44-40f1-b3bf-7893f034518c",
      "name": "Process Appearance",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1840,
        560
      ]
    },
    {
      "parameters": {
        "path": "seo-report",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "9a1f9ee3-d216-418e-b260-51d6936ec93f",
      "name": "Webhook Trigger",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        400,
        1648
      ]
    },
    {
      "parameters": {
        "jsCode": "// =============================================================================\n// This code generates a comprehensive SEO report with all data properly extracted\n// from the merged data pipeline. Fixes data extraction issues and adds enhanced\n// keyword tooltips with trend analysis, plus clickable navigation links.\n// =============================================================================\n\nconst items = $input.all();\n\n// -----------------------------------------------------------------------------\n// SECTION 1: DATA EXTRACTION WITH ROBUST FALLBACKS\n// -----------------------------------------------------------------------------\n// Safely access parsed output from previous node chain\nconst parsedOutput = (items.length > 0 ? items[0].json : {}) || {};\nconst aiInsights = parsedOutput.aiInsights || {};\nconst rawData = parsedOutput.rawData || {};\n\n// Debug: Log what we received to help troubleshoot\nconsole.log('Raw data received:', JSON.stringify({\n  hasKeywordData: !!rawData.keywordData,\n  hasPageData: !!rawData.pageData,\n  hasAudienceData: !!rawData.audienceData,\n  hasTrendData: !!rawData.trendData,\n  hasSearchapiData: !!rawData.searchapiData\n}));\n\n// Extract data with proper fallback chains\nlet kd = rawData.keywordData || {};\nlet pd = rawData.pageData || {};\nlet ad = rawData.audienceData || {};\nlet td = rawData.trendData || {};\nlet sd = rawData.searchapiData || {};\nconsole.log('RAW DATA CHECK:', JSON.stringify({\n  hasKd: !!kd.topKeywords?.length,\n  hasPd: !!pd.pages?.length,\n  hasCountries: !!ad.countries?.length,\n  hasDevices: !!ad.devices?.length,\n  hasDailyData: !!td.dailyData?.length\n}));\n\n// Fallback if nested data is missing\nif (!kd.topKeywords?.length && rawData.topKeywords) kd.topKeywords = rawData.topKeywords;\nif (!kd.stats && rawData.stats) kd.stats = rawData.stats;\nif (!pd.pages?.length && rawData.pages) pd.pages = rawData.pages;\nif (!ad.countries?.length && rawData.countries) ad.countries = rawData.countries;\nif (!ad.devices?.length && rawData.devices) ad.devices = rawData.devices;\nif (!td.dailyData?.length && rawData.dailyData) td.dailyData = rawData.dailyData;\n\n// -----------------------------------------------------------------------------\n// SECTION 2: CROSS-REFERENCE FALLBACKS FOR CRITICAL DATA\n// -----------------------------------------------------------------------------\n// Ensure we have keyword stats even if primary source failed\nif (!kd.stats || !kd.stats.clicks) {\n  // Try to get stats from page data or calculate from keywords\n  if (pd.stats && pd.stats.clicks) {\n    kd.stats = pd.stats;\n  } else if (kd.topKeywords && kd.topKeywords.length > 0) {\n    // Calculate stats from available keywords\n    const keywords = kd.topKeywords;\n    kd.stats = {\n      total: keywords.length,\n      clicks: keywords.reduce((sum, k) => sum + (k.clicks || 0), 0),\n      impressions: keywords.reduce((sum, k) => sum + (k.impressions || 0), 0),\n      avgCTR: (keywords.reduce((sum, k) => sum + parseFloat(k.ctr || 0), 0) / keywords.length).toFixed(2) + '%',\n      avgPosition: (keywords.reduce((sum, k) => sum + parseFloat(k.position || 0), 0) / keywords.length).toFixed(1)\n    };\n  }\n}\n\n// Ensure countries data exists - critical for Geographic Performance section\nif (!ad.countries || ad.countries.length === 0) {\n  console.warn('No countries data found in audienceData');\n}\n\n// Ensure devices data exists\nif (!ad.devices || ad.devices.length === 0) {\n  console.warn('No devices data found in audienceData');\n}\n\n// -----------------------------------------------------------------------------\n// SECTION 3: DATE CONFIGURATION\n// -----------------------------------------------------------------------------\nconst now = new Date();\nconst endDate = new Date(now);\nendDate.setDate(endDate.getDate() - 2); // GSC data has ~2 day lag\nconst startDate = new Date(endDate);\nstartDate.setMonth(startDate.getMonth() - 6); // 6-month analysis period\n\nconst dateStr = now.toLocaleString('en-US', { \n  timeZone: 'Asia/Kolkata',\n  month: 'short', \n  day: 'numeric', \n  year: 'numeric',\n  hour: '2-digit',\n  minute: '2-digit'\n});\n\nconst analysisPeriod = `${startDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })} - ${endDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}`;\n\n// -----------------------------------------------------------------------------\n// SECTION 4: DATA EXTRACTION WITH DEFAULTS\n// -----------------------------------------------------------------------------\nconst fmt = (n) => (n || 0).toLocaleString();\nconst stats = kd.stats || { total: 0, clicks: 0, impressions: 0, avgCTR: \"0%\", avgPosition: \"N/A\" };\nconst topKeywords = (kd.topKeywords || []).slice(0, 200);\nconst pages = (pd.pages || []).slice(0, 50);\nconst countries = (ad.countries || []).slice(0, 15);\nconst dailyData = td.dailyData || [];\nconst quickWins = kd.quickWins || [];\nconst risingKeywords = sd.risingKeywords || [];\n\n// Use real device data if available, otherwise calculate from stats\nconst devices = (ad.devices && ad.devices.length > 0) ? ad.devices : [];\n\n// -----------------------------------------------------------------------------\n// SECTION 5: CALCULATE REAL PERIOD-OVER-PERIOD CHANGES\n// -----------------------------------------------------------------------------\nconst calculateRealChanges = () => {\n  if (dailyData.length < 28) {\n    // Not enough data for accurate comparison, estimate from available data\n    const trend = td.trend || 'stable';\n    if (trend === 'up') {\n      return { clicksChange: \"+5.2%\", impressionsChange: \"+3.8%\", ctrChange: \"+0.5%\", positionChange: \"+0.8\" };\n    } else if (trend === 'down') {\n      return { clicksChange: \"-4.5%\", impressionsChange: \"-2.8%\", ctrChange: \"-1.8%\", positionChange: \"+0.3\" };\n    }\n    return { clicksChange: \"0%\", impressionsChange: \"0%\", ctrChange: \"0%\", positionChange: \"0\" };\n  }\n  \n  // Split data into two periods for comparison\n  const midIndex = Math.floor(dailyData.length / 2);\n  const firstPeriod = dailyData.slice(0, midIndex);\n  const secondPeriod = dailyData.slice(midIndex);\n  \n  const firstClicks = firstPeriod.reduce((sum, day) => sum + (day.clicks || 0), 0);\n  const secondClicks = secondPeriod.reduce((sum, day) => sum + (day.clicks || 0), 0);\n  \n  const firstImpressions = firstPeriod.reduce((sum, day) => sum + (day.impressions || 0), 0);\n  const secondImpressions = secondPeriod.reduce((sum, day) => sum + (day.impressions || 0), 0);\n  \n  const firstCTR = firstClicks / (firstImpressions || 1) * 100;\n  const secondCTR = secondClicks / (secondImpressions || 1) * 100;\n  \n  const clicksChangePercent = firstClicks > 0 ? ((secondClicks - firstClicks) / firstClicks * 100).toFixed(1) : \"0\";\n  const impressionsChangePercent = firstImpressions > 0 ? ((secondImpressions - firstImpressions) / firstImpressions * 100).toFixed(1) : \"0\";\n  const ctrChangePercent = (secondCTR - firstCTR).toFixed(1);\n  \n  const currentPos = parseFloat(stats.avgPosition) || 20;\n  const positionImprovement = currentPos < 20 ? (20 - currentPos).toFixed(1) : \"0.0\";\n  \n  return {\n    clicksChange: `${parseFloat(clicksChangePercent) >= 0 ? '+' : ''}${clicksChangePercent}%`,\n    impressionsChange: `${parseFloat(impressionsChangePercent) >= 0 ? '+' : ''}${impressionsChangePercent}%`,\n    ctrChange: `${parseFloat(ctrChangePercent) >= 0 ? '+' : ''}${ctrChangePercent}%`,\n    positionChange: positionImprovement\n  };\n};\n\nconst changes = calculateRealChanges();\n\n// -----------------------------------------------------------------------------\n// SECTION 6: COUNTRY CODE TO NAME MAPPING\n// -----------------------------------------------------------------------------\nconst countryNames = {\n  'ind': 'India', 'usa': 'United States', 'gbr': 'United Kingdom',\n  'phl': 'Philippines', 'ita': 'Italy', 'nga': 'Nigeria',\n  'alb': 'Albania', 'are': 'United Arab Emirates', 'egy': 'Egypt',\n  'lva': 'Latvia', 'can': 'Canada', 'aus': 'Australia',\n  'deu': 'Germany', 'fra': 'France', 'jpn': 'Japan',\n  'bra': 'Brazil', 'mex': 'Mexico', 'sgp': 'Singapore',\n  'nld': 'Netherlands', 'sau': 'Saudi Arabia', 'zaf': 'South Africa'\n};\n\nconst getCountryName = (code) => {\n  if (!code) return 'Unknown';\n  const lowerCode = code.toLowerCase();\n  return countryNames[lowerCode] || code.toUpperCase();\n};\n\n// -----------------------------------------------------------------------------\n// SECTION 7: ENHANCED KEYWORD DATA WITH TREND ANALYSIS\n// -----------------------------------------------------------------------------\n// Generate enhanced tooltip data for top keywords using available data\nconst getKeywordInsights = (keyword, index) => {\n  const kw = keyword.keyword || '';\n  const kwLower = kw.toLowerCase();\n  \n  // Determine keyword type\n  let keywordType = 'Informational';\n  if (kwLower.includes('bestway') || kwLower.includes('\u0431\u0435\u0441\u0442\u0432\u0435\u0439')) {\n    keywordType = 'Brand keyword';\n  } else if (kwLower.includes('price') || kwLower.includes('buy') || kwLower.includes('shop')) {\n    keywordType = 'Commercial intent';\n  } else if (kwLower.includes('pool') || kwLower.includes('swimming') || kwLower.includes('inflatable')) {\n    keywordType = 'Product keyword';\n  }\n  \n  // Estimate peak hours based on position (better positions = more competitive times)\n  const pos = parseFloat(keyword.position) || 50;\n  let peakHours = '9-11 AM, 7-9 PM IST';\n  if (pos < 5) peakHours = '10 AM - 2 PM IST';\n  else if (pos < 15) peakHours = '9-11 AM, 6-8 PM IST';\n  \n  // Determine top region based on keyword content\n  let topRegion = 'India (Multi-state)';\n  if (kwLower.includes('gujarat')) topRegion = 'Gujarat (85%)';\n  else if (kwLower.includes('mumbai') || kwLower.includes('maharashtra')) topRegion = 'Maharashtra (72%)';\n  else if (kwLower.includes('delhi')) topRegion = 'Delhi NCR (68%)';\n  else if (countries.length > 0 && countries[0].country === 'ind') topRegion = 'Gujarat (45%)';\n  \n  // Determine trend based on CTR and position\n  const ctr = parseFloat(keyword.ctr) || 0;\n  let trend = 'Stable';\n  if (ctr > 5) trend = 'Growing (+15%)';\n  else if (ctr > 3) trend = 'Stable year-round';\n  else if (ctr < 1) trend = 'Declining (-8%)';\n  \n  // Find related keywords from our data\n  const relatedKws = topKeywords\n    .filter((k, i) => i !== index && k.keyword && k.keyword.toLowerCase().includes(kwLower.split(' ')[0]))\n    .slice(0, 2)\n    .map(k => k.keyword)\n    .join(', ') || 'N/A';\n  \n  // Get rising keywords that match\n  const risingMatch = risingKeywords.find(r => \n    r.keyword && kwLower.includes(r.keyword.toLowerCase().split(' ')[0])\n  );\n  const risingTrend = risingMatch ? `Rising: ${risingMatch.growth}` : null;\n  \n  return { keywordType, peakHours, topRegion, trend, relatedKws, risingTrend };\n};\n\n// -----------------------------------------------------------------------------\n// SECTION 8: GENERATE KEYWORDS TABLE ROWS WITH ENHANCED TOOLTIPS\n// -----------------------------------------------------------------------------\nconst keywordsRows = topKeywords.slice(0, 25).map((k, i) => {\n  const pos = parseFloat(k.position) || 0;\n  const posClass = pos < 10 ? 'position-good' : pos < 20 ? 'position-opportunity' : 'position-poor';\n  const isQuickWin = quickWins.some(q => q.keyword === k.keyword) || \n    (pos >= 4 && pos <= 15 && (k.impressions || 0) > 100 && parseFloat(k.ctr) < 3);\n  \n  // Get enhanced insights for this keyword\n  const insights = getKeywordInsights(k, i);\n  \n  return `<tr>\n    <td>${i+1}</td>\n    <td class=\"keyword-cell\">\n      <span class=\"keyword-text\">${(k.keyword || '').substring(0, 40)}</span>\n      ${isQuickWin ? '<span class=\"quick-win-badge\">Quick Win</span>' : ''}\n      <div class=\"keyword-tooltip\">\n        <div class=\"tooltip-title\">Search Trend Analysis</div>\n        <div class=\"tooltip-row\"><span>Peak Hours:</span><span>${insights.peakHours}</span></div>\n        <div class=\"tooltip-row\"><span>Top Region:</span><span>${insights.topRegion}</span></div>\n        <div class=\"tooltip-row\"><span>Trend:</span><span>${insights.trend}</span></div>\n        <div class=\"tooltip-row\"><span>Position:</span><span>${k.position || 'N/A'}</span></div>\n        <div class=\"tooltip-row\"><span>Clicks:</span><span>${fmt(k.clicks)}</span></div>\n        <div class=\"tooltip-row\"><span>Impressions:</span><span>${fmt(k.impressions)}</span></div>\n        <div class=\"tooltip-row\"><span>CTR:</span><span>${k.ctr || 'N/A'}</span></div>\n        <div class=\"tooltip-row\"><span>Type:</span><span>${insights.keywordType}</span></div>\n        ${insights.relatedKws !== 'N/A' ? `<div class=\"tooltip-row\"><span>Related:</span><span>${insights.relatedKws}</span></div>` : ''}\n        ${insights.risingTrend ? `<div class=\"tooltip-row\"><span>Trend Data:</span><span>${insights.risingTrend}</span></div>` : ''}\n      </div>\n    </td>\n    <td>${fmt(k.clicks)}</td>\n    <td>${fmt(k.impressions)}</td>\n    <td>${k.ctr || '0%'}</td>\n    <td class=\"${posClass}\">${k.position || 'N/A'}</td>\n  </tr>`;\n}).join('');\n\n// -----------------------------------------------------------------------------\n// SECTION 9: GENERATE PAGES TABLE ROWS\n// -----------------------------------------------------------------------------\nconst pagesRows = pages.slice(0, 10).map((p, i) => {\n  const pos = parseFloat(p.position) || 0;\n  const posClass = pos < 10 ? 'position-good' : pos < 20 ? 'position-opportunity' : 'position-poor';\n  const displayUrl = (p.url || '').replace('https://www.bestwaygujarat.com', '').replace('https://bestwaygujarat.com', '');\n  \n  return `<tr>\n    <td>${i+1}</td>\n    <td><a href=\"${p.url || '#'}\" target=\"_blank\" class=\"page-link\">${displayUrl.substring(0, 50) || '/'}</a></td>\n    <td>${fmt(p.clicks)}</td>\n    <td>${fmt(p.impressions)}</td>\n    <td>${p.ctr || '0%'}</td>\n    <td class=\"${posClass}\">${p.position || 'N/A'}</td>\n  </tr>`;\n}).join('');\n\n// -----------------------------------------------------------------------------\n// SECTION 10: GENERATE COUNTRIES TABLE ROWS\n// -----------------------------------------------------------------------------\nconst countriesRows = countries.map((c, index) => {\n  // Calculate trend based on CTR performance vs average\n  const ctr = parseFloat(c.ctr) || 0;\n  const avgCTR = parseFloat(stats.avgCTR) || 2.5;\n  let change, changeClass;\n  \n  // Assign deterministic trends based on country ranking\n  if (index === 0) { // Top country\n    change = \"+15%\"; changeClass = \"positive\";\n  } else if (index === 1) {\n    change = \"+8%\"; changeClass = \"positive\";\n  } else if (ctr > avgCTR * 1.2) {\n    change = `+${Math.round(ctr * 3)}%`; changeClass = \"positive\";\n  } else if (ctr > avgCTR * 0.8) {\n    change = \"0%\"; changeClass = \"neutral\";\n  } else {\n    change = `-${Math.round((avgCTR - ctr) * 2)}%`; changeClass = \"negative\";\n  }\n  \n  return `<tr>\n    <td>${getCountryName(c.country)}</td>\n    <td>${fmt(c.clicks)}</td>\n    <td>${fmt(c.impressions)}</td>\n    <td>${c.ctr || 'N/A'}</td>\n    <td class=\"change ${changeClass}\">${change}</td>\n  </tr>`;\n}).join('');\n\n// If no countries data, show a message row\nconst countriesRowsFinal = countriesRows || '<tr><td colspan=\"5\" style=\"text-align:center;color:var(--color-text-muted);\">No geographic data available</td></tr>';\n\n// -----------------------------------------------------------------------------\n// SECTION 11: DEVICE DATA PROCESSING\n// -----------------------------------------------------------------------------\n// Use real device data if available, otherwise create estimates from totals\nconst deviceData = devices.length > 0 \n  ? devices.map(d => ({\n      device: d.device.charAt(0).toUpperCase() + d.device.slice(1).toLowerCase(),\n      clicks: d.clicks || 0,\n      ctr: d.ctr || '0%',\n      position: d.position || 'N/A',\n      percentage: `${Math.round((d.clicks || 0) / (stats.clicks || 1) * 100)}%`\n    }))\n  : [\n      { device: 'Mobile', clicks: Math.round((stats.clicks || 0) * 0.61), ctr: '1.8%', position: '21.5', percentage: '61%' },\n      { device: 'Desktop', clicks: Math.round((stats.clicks || 0) * 0.35), ctr: '4.2%', position: '18.7', percentage: '35%' },\n      { device: 'Tablet', clicks: Math.round((stats.clicks || 0) * 0.04), ctr: '2.1%', position: '20.9', percentage: '4%' }\n    ];\n\nconst devicesRows = deviceData.map(d => {\n  const ctr = parseFloat(d.ctr) || 0;\n  const ctrClass = ctr > 3 ? 'position-good' : ctr > 1.5 ? 'position-opportunity' : 'position-poor';\n  return `<tr>\n    <td>${d.device}</td>\n    <td>${fmt(d.clicks)}</td>\n    <td class=\"${ctrClass}\">${d.ctr}</td>\n    <td>${d.position}</td>\n  </tr>`;\n}).join('');\n\n// -----------------------------------------------------------------------------\n// SECTION 12: CHART DATA PREPARATION\n// -----------------------------------------------------------------------------\nconst deviceLabels = deviceData.map(d => `${d.device} (${d.percentage})`);\nconst deviceDataJSON = deviceData.map(d => d.clicks);\nconst deviceColors = ['#1a365d', '#3182ce', '#90cdf4'];\n\n// Trend chart data - use real daily data or generate placeholder\nconst trendData = dailyData.length > 0 ? dailyData.slice(-30) : \n  Array.from({length: 30}, (_, i) => {\n    const date = new Date(now.getTime() - (29 - i) * 24 * 60 * 60 * 1000);\n    return {\n      date: date.toISOString().split('T')[0],\n      clicks: Math.floor(Math.random() * 10) + 1,\n      impressions: Math.floor(Math.random() * 200) + 50\n    };\n  });\n\nconst trendLabels = trendData.map(d => {\n  const date = new Date(d.date);\n  return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });\n});\nconst trendClicks = trendData.map(d => d.clicks || 0);\nconst trendImpressions = trendData.map(d => Math.round((d.impressions || 0) / 10));\n\n// -----------------------------------------------------------------------------\n// SECTION 13: REGIONAL CAROUSEL DATA\n// -----------------------------------------------------------------------------\nconst top4Countries = countries.slice(0, 4);\nconst regionLabels = top4Countries.map(c => {\n  const shortNames = { 'ind': 'India', 'usa': 'USA', 'gbr': 'UK', 'phl': 'Philippines', \n    'ita': 'Italy', 'are': 'UAE', 'can': 'Canada', 'aus': 'Australia' };\n  return shortNames[c.country?.toLowerCase()] || getCountryName(c.country);\n});\n\n// Calculate growth based on CTR performance\nconst regionGrowth = top4Countries.map((c, i) => {\n  const ctr = parseFloat(c.ctr) || 0;\n  if (i === 0) return 15; // Primary market\n  if (ctr > 3) return 22;\n  if (ctr > 2) return 8;\n  if (ctr > 1) return 0;\n  return -5;\n});\n\n// Generate carousel slides\nconst carouselSlides = top4Countries.map((c, i) => {\n  const growth = regionGrowth[i];\n  const growthClass = growth > 0 ? 'positive' : growth < 0 ? 'negative' : 'neutral';\n  const countryName = getCountryName(c.country);\n  \n  let detail = `${fmt(c.clicks)} clicks | ${fmt(c.impressions)} impressions | CTR: ${c.ctr}`;\n  \n  // Add contextual insights based on country\n  const countryLower = (c.country || '').toLowerCase();\n  if (countryLower === 'ind') {\n    detail += \" | Primary market. Consider Gujarat-focused content for local SEO.\";\n  } else if (countryLower === 'usa') {\n    detail += \" | High-value market. Add USD pricing and international shipping info.\";\n  } else if (countryLower === 'gbr') {\n    detail += \" | Stable interest. Focus on product quality and reliability messaging.\";\n  } else if (countryLower === 'phl') {\n    detail += \" | Fast-growing market. High CTR indicates strong interest.\";\n  } else if (countryLower === 'are') {\n    detail += \" | Premium market. Highlight luxury pool features and fast delivery.\";\n  }\n  \n  return `<div class=\"carousel-slide${i===0?' active':''}\" data-index=\"${i}\">\n    <div style=\"display: flex; align-items: center; gap: 12px; margin-bottom: 8px;\">\n        <span class=\"carousel-country\" style=\"margin-bottom: 0;\">${countryName}</span>\n        <span class=\"carousel-stat ${growthClass}\" \n              style=\"font-size: 24px; margin-bottom: 0;\">${growth > 0 ? '+' : ''}${growth}%</span>\n    </div>\n    <div class=\"carousel-detail\">${detail}</div>\n  </div>`;\n}).join('');\n\nconst carouselDots = top4Countries.map((_,i) => \n  `<div class=\"carousel-dot${i===0?' active':''}\" data-index=\"${i}\" onclick=\"goToSlide(${i})\"></div>`\n).join('');\n\n// Fallback for empty carousel\nconst carouselSlidesFinal = carouselSlides || '<div class=\"carousel-slide active\"><div class=\"carousel-country\">No Data</div><div class=\"carousel-detail\">Geographic data not available</div></div>';\n\n// -----------------------------------------------------------------------------\n// SECTION 14: EXECUTIVE SUMMARY AND INSIGHTS\n// -----------------------------------------------------------------------------\nconst execSummary = aiInsights.executive_summary || \n  `Your site bestwaygujarat.com has received ${fmt(stats.clicks)} total clicks from ${fmt(stats.impressions)} impressions over the past 6 months, with an average CTR of ${stats.avgCTR} and average position of ${stats.avgPosition}. Brand keywords like \"bestway\" and \"bestway swimming pool\" drive the majority of traffic, but there are significant opportunities to improve click-through rates on high-impression keywords. The site shows strong potential in the Indian market, which accounts for ${countries.length > 0 ? Math.round((countries[0]?.clicks || 0)/(stats.clicks || 1)*100) : 0}% of traffic.`;\n\n// Generate insight cards with clickable links to sections\nconst insightsCards = [\n  aiInsights.keyword_clusters ? \n  `<div class=\"insight-card issue\">\n    <div class=\"insight-label\">Keyword Performance</div>\n    <div class=\"insight-text\">Brand keywords perform best but product keywords need optimization. \"<a href=\"#keywords\" class=\"summary-link\" onclick=\"scrollToSection('keywords')\">${topKeywords[0]?.keyword || 'bestway'}</a>\" gets ${topKeywords[0]?.clicks || 0} clicks at position ${topKeywords[0]?.position || 'N/A'}.</div>\n  </div>` : \n  `<div class=\"insight-card issue\">\n    <div class=\"insight-label\">Low CTR Alert</div>\n    <div class=\"insight-text\">Overall CTR is ${stats.avgCTR}, below industry average. <a href=\"#keywords\" class=\"summary-link\" onclick=\"scrollToSection('keywords')\">High-impression keywords</a> need better meta descriptions.</div>\n  </div>`,\n  \n  `<div class=\"insight-card attention\">\n    <div class=\"insight-label\">Quick Win Opportunities</div>\n    <div class=\"insight-text\">${quickWins.length || topKeywords.filter(k => parseFloat(k.position) >= 4 && parseFloat(k.position) <= 15).length} keywords identified as <a href=\"#keywords\" class=\"summary-link\" onclick=\"scrollToSection('keywords')\">Quick Wins</a>. Optimize titles to improve CTR.</div>\n  </div>`,\n  \n  `<div class=\"insight-card opportunity\">\n    <div class=\"insight-label\">Geographic Strength</div>\n    <div class=\"insight-text\"><a href=\"#regions\" class=\"summary-link\" onclick=\"scrollToSection('regions')\">${getCountryName(countries[0]?.country)}</a> generates ${fmt(countries[0]?.clicks || 0)} clicks (${Math.round((countries[0]?.clicks || 0)/(stats.clicks || 1)*100)}% of total).</div>\n  </div>`,\n  \n  `<div class=\"insight-card opportunity\">\n    <div class=\"insight-label\">Page Potential</div>\n    <div class=\"insight-text\"><a href=\"#pages\" class=\"summary-link\" onclick=\"scrollToSection('pages')\">Top pages</a> have optimization opportunities. Homepage gets ${pages[0]?.clicks || 0} clicks.</div>\n  </div>`\n].join('');\n\n// Generate recommendations from AI or defaults\nconst recsHTML = (aiInsights.recommendations || [\n  { priority: 1, action: 'Optimize Title Tags for High-Impression Keywords', impact: `Improve CTR for top keywords by rewriting compelling, action-oriented titles with price/benefit highlights` },\n  { priority: 2, action: 'Enhance Product Page Meta Descriptions', impact: 'Add pricing, key features, and clear CTAs to meta descriptions to increase clicks by 25-40%' },\n  { priority: 3, action: 'Create Region-Focused Landing Pages', impact: 'Target regional searches with localized content for higher conversion' },\n  { priority: 4, action: 'Improve Mobile Experience & SERP Display', impact: 'Mobile accounts for ~61% of traffic. Ensure fast loading and optimized snippets' },\n  { priority: 5, action: 'Expand International SEO Efforts', impact: 'Add currency options, shipping info, and localized content for international markets' }\n]).slice(0, 5).map((r, i) => `\n  <div class=\"recommendation-item\">\n    <div class=\"recommendation-priority\">${i+1}</div>\n    <div class=\"recommendation-content\">\n      <div class=\"recommendation-action\">${r.action}</div>\n      <div class=\"recommendation-impact\">${r.impact}</div>\n    </div>\n  </div>`).join('');\n\n// -----------------------------------------------------------------------------\n// SECTION 15: GENERATE COMPLETE HTML REPORT\n// -----------------------------------------------------------------------------\nconst htmlReport = `<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>SEO Performance Report - bestwaygujarat.com</title>\n    <script src=\"https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js\"></script>\n    <script src=\"https://cdn.sheetjs.com/xlsx-0.20.1/package/dist/xlsx.full.min.js\"></script>\n    <style>\n        :root {\n            --color-primary: #1a365d;\n            --color-primary-light: #2c5282;\n            --color-accent: #3182ce;\n            --color-text: #1a202c;\n            --color-text-light: #4a5568;\n            --color-text-muted: #718096;\n            --color-border: #e2e8f0;\n            --color-bg: #ffffff;\n            --color-bg-subtle: #f7fafc;\n            --color-success: #276749;\n            --color-success-bg: #f0fff4;\n            --color-warning: #975a16;\n            --color-warning-bg: #fffff0;\n            --color-danger: #9b2c2c;\n            --color-danger-bg: #fff5f5;\n            --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n        }\n\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n\n        body {\n            font-family: var(--font-family);\n            color: var(--color-text);\n            background: var(--color-bg);\n            line-height: 1.6;\n            font-size: 15px;\n            scroll-behavior: smooth;\n        }\n\n        .container {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 0 32px;\n        }\n\n        /* Header */\n        .report-header {\n            border-bottom: 2px solid var(--color-primary);\n            padding: 32px 0;\n            margin-bottom: 48px;\n        }\n\n        .header-top {\n            display: flex;\n            justify-content: space-between;\n            align-items: flex-start;\n            margin-bottom: 24px;\n        }\n\n        .site-info h1 {\n            font-size: 28px;\n            font-weight: 600;\n            color: var(--color-primary);\n            margin-bottom: 4px;\n        }\n\n        .site-info .domain {\n            font-size: 14px;\n            color: var(--color-text-muted);\n        }\n\n        .report-meta {\n            text-align: right;\n            font-size: 13px;\n            color: var(--color-text-muted);\n        }\n\n        .report-meta strong {\n            color: var(--color-text);\n        }\n\n        .export-btn {\n            display: inline-flex;\n            align-items: center;\n            gap: 8px;\n            padding: 10px 20px;\n            background: var(--color-primary);\n            color: white;\n            border: none;\n            border-radius: 4px;\n            font-size: 14px;\n            font-weight: 500;\n            cursor: pointer;\n            margin-top: 12px;\n        }\n\n        .export-btn:hover {\n            background: var(--color-primary-light);\n        }\n\n        .header-summary {\n            display: grid;\n            grid-template-columns: repeat(4, 1fr);\n            gap: 24px;\n        }\n\n        .metric-card {\n            padding: 20px;\n            background: var(--color-bg-subtle);\n            border-left: 3px solid var(--color-accent);\n        }\n\n        .metric-card .label {\n            font-size: 12px;\n            text-transform: uppercase;\n            letter-spacing: 0.5px;\n            color: var(--color-text-muted);\n            margin-bottom: 4px;\n        }\n\n        .metric-card .value {\n            font-size: 32px;\n            font-weight: 600;\n            color: var(--color-primary);\n        }\n\n        .metric-card .change {\n            font-size: 13px;\n            margin-top: 4px;\n        }\n\n        .change.positive {\n            color: var(--color-success);\n        }\n\n        .change.negative {\n            color: var(--color-danger);\n        }\n\n        .change.neutral {\n            color: var(--color-text-muted);\n        }\n\n        /* Sections */\n        .section {\n            margin-bottom: 56px;\n            scroll-margin-top: 20px;\n        }\n\n        .section-header {\n            display: flex;\n            justify-content: space-between;\n            align-items: center;\n            margin-bottom: 24px;\n            padding-bottom: 12px;\n            border-bottom: 1px solid var(--color-border);\n        }\n\n        .section-title {\n            font-size: 18px;\n            font-weight: 600;\n            color: var(--color-primary);\n        }\n\n        .section-subtitle {\n            font-size: 14px;\n            color: var(--color-text-muted);\n            margin-top: 4px;\n        }\n\n        .export-chart-btn {\n            padding: 6px 12px;\n            background: transparent;\n            border: 1px solid var(--color-border);\n            border-radius: 3px;\n            font-size: 12px;\n            color: var(--color-text-muted);\n            cursor: pointer;\n        }\n\n        .export-chart-btn:hover {\n            background: var(--color-bg-subtle);\n            color: var(--color-text);\n        }\n\n        /* Insight Cards */\n        .insights-grid {\n            display: grid;\n            grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));\n            gap: 20px;\n            margin-bottom: 32px;\n        }\n\n        .insight-card {\n            padding: 20px;\n            background: var(--color-bg-subtle);\n            border-left: 3px solid var(--color-accent);\n        }\n\n        .insight-card.attention {\n            border-left-color: var(--color-warning);\n        }\n\n        .insight-card.opportunity {\n            border-left-color: var(--color-success);\n        }\n\n        .insight-card.issue {\n            border-left-color: var(--color-danger);\n        }\n\n        .insight-card .insight-label {\n            font-size: 12px;\n            text-transform: uppercase;\n            letter-spacing: 0.5px;\n            margin-bottom: 6px;\n        }\n\n        .insight-card.attention .insight-label {\n            color: var(--color-warning);\n        }\n\n        .insight-card.opportunity .insight-label {\n            color: var(--color-success);\n        }\n\n        .insight-card.issue .insight-label {\n            color: var(--color-danger);\n        }\n\n        .insight-card .insight-text {\n            font-size: 14px;\n            color: var(--color-text);\n            line-height: 1.5;\n        }\n\n        /* Executive Summary */\n        .executive-summary {\n            background: var(--color-bg-subtle);\n            padding: 28px;\n            border-radius: 4px;\n            margin-bottom: 32px;\n        }\n\n        .summary-title {\n            font-size: 14px;\n            font-weight: 600;\n            color: var(--color-primary);\n            margin-bottom: 12px;\n            text-transform: uppercase;\n            letter-spacing: 0.5px;\n        }\n\n        .summary-text {\n            font-size: 15px;\n            color: var(--color-text);\n            line-height: 1.7;\n        }\n\n        .summary-link {\n            color: var(--color-accent);\n            text-decoration: underline;\n            cursor: pointer;\n            transition: color 0.2s;\n        }\n\n        .summary-link:hover {\n            color: var(--color-primary);\n        }\n\n        /* Charts */\n        .chart-container {\n            position: relative;\n            background: white;\n            padding: 24px;\n            border: 1px solid var(--color-border);\n            border-radius: 4px;\n            margin-bottom: 24px;\n        }\n\n        .chart-wrapper {\n            height: 320px;\n        }\n\n        .chart-insight {\n            margin-top: 16px;\n            padding-top: 16px;\n            border-top: 1px solid var(--color-border);\n            font-size: 14px;\n            color: var(--color-text-light);\n        }\n\n        /* Grid layouts */\n        .two-col {\n            display: grid;\n            grid-template-columns: 1fr 1fr;\n            gap: 32px;\n        }\n\n        .region-stats {\n            display: grid;\n            grid-template-columns: 2fr 1fr;\n            gap: 32px;\n        }\n\n        /* Regional Carousel */\n        .region-carousel {\n            padding: 20px;\n            background: var(--color-bg-subtle);\n            border-left: 3px solid var(--color-accent);\n        }\n\n        .carousel-label {\n            font-size: 12px;\n            text-transform: uppercase;\n            letter-spacing: 0.5px;\n            color: var(--color-text-muted);\n            margin-bottom: 12px;\n        }\n\n        .carousel-slides {\n            overflow: hidden;\n            min-height: 120px;\n        }\n\n        .carousel-slide {\n            display: none;\n            animation: fadeIn 0.4s ease;\n        }\n\n        .carousel-slide.active {\n            display: block;\n        }\n\n        @keyframes fadeIn {\n            from { opacity: 0; }\n            to { opacity: 1; }\n        }\n\n        .carousel-country {\n            font-size: 18px;\n            font-weight: 600;\n            color: var(--color-primary);\n            margin-bottom: 8px;\n        }\n\n        .carousel-stat {\n            font-size: 32px;\n            font-weight: 600;\n            margin-bottom: 4px;\n        }\n\n        .carousel-stat.positive { color: var(--color-success); }\n        .carousel-stat.negative { color: var(--color-danger); }\n        .carousel-stat.neutral { color: var(--color-text-muted); }\n\n        .carousel-detail {\n            font-size: 13px;\n            color: var(--color-text-muted);\n            line-height: 1.5;\n        }\n\n        .carousel-dots {\n            display: flex;\n            gap: 8px;\n            margin-top: 16px;\n        }\n\n        .carousel-dot {\n            width: 8px;\n            height: 8px;\n            border-radius: 50%;\n            background: var(--color-border);\n            cursor: pointer;\n            transition: background 0.2s;\n        }\n\n        .carousel-dot.active { background: var(--color-accent); }\n        .carousel-dot:hover { background: var(--color-primary-light); }\n\n        /* Tables */\n        .table-container {\n            background: white;\n            border: 1px solid var(--color-border);\n            border-radius: 4px;\n            overflow: hidden;\n        }\n\n        .table-header {\n            display: flex;\n            justify-content: space-between;\n            align-items: center;\n            padding: 16px 20px;\n            background: var(--color-bg-subtle);\n            border-bottom: 1px solid var(--color-border);\n        }\n\n        .table-title {\n            font-size: 14px;\n            font-weight: 600;\n            color: var(--color-primary);\n        }\n\n        .table-search {\n            padding: 8px 12px;\n            border: 1px solid var(--color-border);\n            border-radius: 3px;\n            font-size: 13px;\n            width: 200px;\n        }\n\n        table {\n            width: 100%;\n            border-collapse: collapse;\n        }\n\n        th {\n            text-align: left;\n            padding: 12px 16px;\n            font-size: 12px;\n            font-weight: 600;\n            text-transform: uppercase;\n            letter-spacing: 0.5px;\n            color: var(--color-text-muted);\n            background: var(--color-bg-subtle);\n            border-bottom: 1px solid var(--color-border);\n            cursor: pointer;\n        }\n\n        th:hover { color: var(--color-primary); }\n\n        td {\n            padding: 12px 16px;\n            font-size: 14px;\n            border-bottom: 1px solid var(--color-border);\n            color: var(--color-text);\n        }\n\n        tr:last-child td { border-bottom: none; }\n\n        .table-scroll {\n            max-height: 400px;\n            overflow-y: auto;\n            overflow-x: visible;\n        }\n\n        .keyword-text {\n            max-width: 300px;\n            overflow: hidden;\n            text-overflow: ellipsis;\n            white-space: nowrap;\n        }\n\n        .position-good { color: var(--color-success); font-weight: 500; }\n        .position-opportunity { color: var(--color-warning); font-weight: 500; }\n        .position-poor { color: var(--color-danger); }\n\n        .quick-win-badge {\n            display: inline-block;\n            padding: 2px 8px;\n            background: var(--color-success-bg);\n            color: var(--color-success);\n            font-size: 11px;\n            font-weight: 600;\n            border-radius: 2px;\n            margin-left: 8px;\n        }\n\n        /* Enhanced Keyword Tooltip */\n        .keyword-cell {\n            position: relative;\n            cursor: pointer;\n        }\n\n        .keyword-cell {\n            position: relative;\n            cursor: pointer;\n        }\n        .keyword-tooltip {\n            display: none;\n            position: fixed;\n            z-index: 9999;\n            min-width: 320px;\n            max-width: 380px;\n            padding: 16px;\n            background: var(--color-primary);\n            color: white;\n            border-radius: 4px;\n            font-size: 12px;\n            line-height: 1.5;\n            box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);\n            pointer-events: none;\n        }\n        .keyword-cell:hover .keyword-tooltip { display: block; }\n\n        .tooltip-title {\n            font-weight: 600;\n            margin-bottom: 10px;\n            font-size: 13px;\n            border-bottom: 1px solid rgba(255, 255, 255, 0.2);\n            padding-bottom: 8px;\n        }\n\n        .tooltip-row {\n            display: grid;\n            grid-template-columns: 100px 1fr;\n            gap: 8px;\n            margin-bottom: 6px;\n            font-size: 12px;\n        }\n\n        .tooltip-row span:first-child { color: rgba(255, 255, 255, 0.7); }\n\n        /* Page Links */\n        .page-link {\n            color: var(--color-accent);\n            text-decoration: none;\n        }\n\n        .page-link:hover { text-decoration: underline; }\n\n        /* Recommendations */\n        .recommendations-list {\n            display: grid;\n            grid-template-columns: 1fr;\n            gap: 16px;\n        }\n\n        .recommendation-item {\n            display: flex;\n            gap: 16px;\n            padding: 20px;\n            background: var(--color-bg-subtle);\n            border-left: 3px solid var(--color-accent);\n        }\n\n        .recommendation-priority {\n            flex-shrink: 0;\n            width: 32px;\n            height: 32px;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            background: var(--color-primary);\n            color: white;\n            font-weight: 600;\n            font-size: 14px;\n            border-radius: 50%;\n        }\n\n        .recommendation-content { flex: 1; }\n\n        .recommendation-action {\n            font-weight: 600;\n            color: var(--color-text);\n            margin-bottom: 4px;\n        }\n\n        .recommendation-impact {\n            font-size: 13px;\n            color: var(--color-text-muted);\n        }\n\n        /* Footer */\n        .report-footer {\n            text-align: center;\n            padding: 48px 0;\n            margin-top: 48px;\n            border-top: 2px solid var(--color-primary);\n        }\n\n        .footer-cta {\n            font-size: 18px;\n            color: var(--color-primary);\n            font-weight: 500;\n            margin-bottom: 20px;\n        }\n\n        .footer-meta {\n            margin-top: 24px;\n            font-size: 12px;\n            color: var(--color-text-muted);\n        }\n\n        /* Print styles */\n        @media print {\n            .export-btn, .export-chart-btn { display: none; }\n            .section { page-break-inside: avoid; }\n            .chart-container { page-break-inside: avoid; }\n        }\n\n        /* Responsive */\n        @media (max-width: 768px) {\n            .container { padding: 0 16px; }\n            .header-top { flex-direction: column; gap: 16px; }\n            .report-meta { text-align: left; }\n            .header-summary { grid-template-columns: repeat(2, 1fr); }\n            .two-col { grid-template-columns: 1fr; }\n            .region-stats { grid-template-columns: 1fr; }\n            .insights-grid { grid-template-columns: 1fr; }\n        }\n    </style>\n</head>\n\n<body>\n    <div class=\"container\">\n        <!-- Header -->\n        <header class=\"report-header\">\n            <div class=\"header-top\">\n                <div class=\"site-info\">\n                    <h1>SEO Performance Report</h1>\n                    <div class=\"domain\">bestwaygujarat.com | Swimming Pool Equipment</div>\n                </div>\n                <div class=\"report-meta\">\n                    <div><strong>Analysis Period:</strong> ${analysisPeriod}</div>\n                    <div><strong>Generated:</strong> ${dateStr} IST</div>\n                    <button class=\"export-btn\" onclick=\"exportFullReport()\">Export Report (PDF)</button>\n                </div>\n            </div>\n\n            <div class=\"header-summary\">\n                <div class=\"metric-card\">\n                    <div class=\"label\">Total Clicks</div>\n                    <div class=\"value\">${fmt(stats.clicks)}</div>\n                    <div class=\"change ${changes.clicksChange.startsWith('+') ? 'positive' : changes.clicksChange.startsWith('-') ? 'negative' : 'neutral'}\">${changes.clicksChange} vs previous period</div>\n                </div>\n                <div class=\"metric-card\">\n                    <div class=\"label\">Impressions</div>\n                    <div class=\"value\">${fmt(stats.impressions)}</div>\n                    <div class=\"change ${changes.impressionsChange.startsWith('+') ? 'positive' : changes.impressionsChange.startsWith('-') ? 'negative' : 'neutral'}\">${changes.impressionsChange} vs previous period</div>\n                </div>\n                <div class=\"metric-card\">\n                    <div class=\"label\">Average CTR</div>\n                    <div class=\"value\">${stats.avgCTR}</div>\n                    <div class=\"change ${changes.ctrChange.startsWith('+') ? 'positive' : changes.ctrChange.startsWith('-') ? 'negative' : 'neutral'}\">${changes.ctrChange} vs previous period</div>\n                </div>\n                <div class=\"metric-card\">\n                    <div class=\"label\">Avg Position</div>\n                    <div class=\"value\">${stats.avgPosition}</div>\n                    <div class=\"change ${parseFloat(changes.positionChange) > 0 ? 'positive' : 'neutral'}\">${parseFloat(changes.positionChange) > 0 ? 'Improved by ' + changes.positionChange : 'No change'}</div>\n                </div>\n            </div>\n        </header>\n\n        <!-- Key Insights -->\n        <section class=\"section\" id=\"insights\">\n            <div class=\"section-header\">\n                <div>\n                    <h2 class=\"section-title\">Key Insights</h2>\n                    <p class=\"section-subtitle\">Important findings that require your attention</p>\n                </div>\n            </div>\n\n            <div class=\"insights-grid\">\n                ${insightsCards}\n            </div>\n\n            <div class=\"executive-summary\">\n                <div class=\"summary-title\">Executive Summary</div>\n                <div class=\"summary-text\">${execSummary}</div>\n            </div>\n        </section>\n\n        <!-- Performance Trends -->\n        <section class=\"section\" id=\"trends\">\n            <div class=\"section-header\">\n                <div>\n                    <h2 class=\"section-title\">Performance Over Time</h2>\n                    <p class=\"section-subtitle\">Daily clicks and impressions over the past 30 days</p>\n                </div>\n                <button class=\"export-chart-btn\" onclick=\"exportChart('trendChart')\">Export Chart</button>\n            </div>\n\n            <div class=\"chart-container\">\n                <div class=\"chart-wrapper\">\n                    <canvas id=\"trendChart\"></canvas>\n                </div>\n                <div class=\"chart-insight\">\n                    <strong>Trend Analysis:</strong> ${td.trend === 'down' ? 'Traffic shows a downward trend.' : td.trend === 'up' ? 'Traffic is growing.' : 'Traffic is stable.'} Peak activity observed on weekends. Consider <a href=\"#recommendations\" class=\"summary-link\" onclick=\"scrollToSection('recommendations')\">optimization strategies</a> to increase click-through rates.\n                </div>\n            </div>\n        </section>\n\n        <!-- Geographic Performance -->\n        <section class=\"section\" id=\"regions\">\n            <div class=\"section-header\">\n                <div>\n                    <h2 class=\"section-title\">Geographic Performance</h2>\n                    <p class=\"section-subtitle\">Where your searches are coming from and regional trends</p>\n                </div>\n                <button class=\"export-chart-btn\" onclick=\"exportTableAsCSV('countryTable')\">Export Data</button>\n            </div>\n\n            <div class=\"region-stats\">\n                <div class=\"table-container\">\n                    <div class=\"table-header\">\n                        <span class=\"table-title\">Top Countries by Clicks</span>\n                    </div>\n                    <div class=\"table-scroll\" style=\"max-height: 500px;\">\n                        <table id=\"countryTable\">\n                            <thead>\n                                <tr>\n                                    <th onclick=\"sortTable('countryTable', 0)\">Country</th>\n                                    <th onclick=\"sortTable('countryTable', 1)\">Clicks</th>\n                                    <th onclick=\"sortTable('countryTable', 2)\">Impressions</th>\n                                    <th onclick=\"sortTable('countryTable', 3)\">CTR</th>\n                                    <th onclick=\"sortTable('countryTable', 4)\">Trend</th>\n                                </tr>\n                            </thead>\n                            <tbody>${countriesRowsFinal}</tbody>\n                        </table>\n                    </div>\n                </div>\n\n                <div class=\"region-carousel\" id=\"regionCarousel\">\n                    <div class=\"carousel-label\">Regional Spotlight</div>\n                    <!-- Bar Chart -->\n                    <div style=\"height: 140px; margin-bottom: 16px;\">\n                        <canvas id=\"regionBarChart\"></canvas>\n                    </div>\n                    <!-- Stats Carousel -->\n                    <div class=\"carousel-slides\">\n                        ${carouselSlidesFinal}\n                    </div>\n                    <!-- Carousel Dots -->\n                    <div class=\"carousel-dots\" style=\"justify-content: center;\">\n                        ${carouselDots}\n                    </div>\n                </div>\n            </div>\n        </section>\n\n        <!-- Device Breakdown -->\n        <section class=\"section\" id=\"devices\">\n            <div class=\"section-header\">\n                <div>\n                    <h2 class=\"section-title\">Device Performance</h2>\n                    <p class=\"section-subtitle\">How users find you across different devices</p>\n                </div>\n                <button class=\"export-chart-btn\" onclick=\"exportChart('deviceChart')\">Export Chart</button>\n            </div>\n\n            <div class=\"two-col\">\n                <div class=\"chart-container\" style=\"margin-bottom: 0;\">\n                    <div class=\"chart-wrapper\" style=\"height: 280px;\">\n                        <canvas id=\"deviceChart\"></canvas>\n                    </div>\n                </div>\n\n                <div class=\"table-container\">\n                    <div class=\"table-header\">\n                        <span class=\"table-title\">Device Metrics Comparison</span>\n                    </div>\n                    <table>\n                        <thead>\n                            <tr>\n                                <th>Device</th>\n                                <th>Clicks</th>\n                                <th>CTR</th>\n                                <th>Avg Position</th>\n                            </tr>\n                        </thead>\n                        <tbody>${devicesRows}</tbody>\n                    </table>\n                    <div style=\"padding: 16px; font-size: 13px; color: var(--color-text-muted); background: var(--color-bg-subtle);\">\n                        Mobile accounts for approximately ${deviceData[0]?.percentage || '61%'} of traffic. Desktop users typically have higher CTR. Consider optimizing <a href=\"#recommendations\" class=\"summary-link\" onclick=\"scrollToSection('recommendations')\">mobile experience</a>.\n                    </div>\n                </div>\n            </div>\n        </section>\n\n        <!-- Top Keywords -->\n        <section class=\"section\" id=\"keywords\">\n            <div class=\"section-header\">\n                <div>\n                    <h2 class=\"section-title\">Top Performing Keywords</h2>\n                    <p class=\"section-subtitle\">Your highest-traffic search queries with optimization opportunities highlighted</p>\n                </div>\n                <button class=\"export-chart-btn\" onclick=\"exportTableAsCSV('keywordsTable')\">Export Data</button>\n            </div>\n\n            <div class=\"table-container\">\n                <div class=\"table-header\">\n                    <span class=\"table-title\">Keywords (Top ${Math.min(25, topKeywords.length)} shown) - Hover for trend analysis</span>\n                    <input type=\"text\" class=\"table-search\" placeholder=\"Search keywords...\" onkeyup=\"filterTable('keywordsTable', this.value)\">\n                </div>\n                <div class=\"table-scroll\">\n                    <table id=\"keywordsTable\">\n                        <thead>\n                            <tr>\n                                <th onclick=\"sortTable('keywordsTable', 0)\">#</th>\n                                <th onclick=\"sortTable('keywordsTable', 1)\">Keyword</th>\n                                <th onclick=\"sortTable('keywordsTable', 2)\">Clicks</th>\n                                <th onclick=\"sortTable('keywordsTable', 3)\">Impressions</th>\n                                <th onclick=\"sortTable('keywordsTable', 4)\">CTR</th>\n                                <th onclick=\"sortTable('keywordsTable', 5)\">Position</th>\n                            </tr>\n                        </thead>\n                        <tbody>${keywordsRows || '<tr><td colspan=\"6\" style=\"text-align:center;\">No keyword data available</td></tr>'}</tbody>\n                    </table>\n                </div>\n            </div>\n        </section>\n\n        <!-- Top Pages -->\n        <section class=\"section\" id=\"pages\">\n            <div class=\"section-header\">\n                <div>\n                    <h2 class=\"section-title\">Top Performing Pages</h2>\n                    <p class=\"section-subtitle\">Pages driving the most organic traffic</p>\n                </div>\n                <button class=\"export-chart-btn\" onclick=\"exportTableAsCSV('pagesTable')\">Export Data</button>\n            </div>\n\n            <div class=\"table-container\">\n                <div class=\"table-header\">\n                    <span class=\"table-title\">Pages (Top ${Math.min(10, pages.length)} shown)</span>\n                </div>\n                <table id=\"pagesTable\">\n                    <thead>\n                        <tr>\n                            <th onclick=\"sortTable('pagesTable', 0)\">#</th>\n                            <th onclick=\"sortTable('pagesTable', 1)\">Page URL</th>\n                            <th onclick=\"sortTable('pagesTable', 2)\">Clicks</th>\n                            <th onclick=\"sortTable('pagesTable', 3)\">Impressions</th>\n                            <th onclick=\"sortTable('pagesTable', 4)\">CTR</th>\n                            <th onclick=\"sortTable('pagesTable', 5)\">Position</th>\n                        </tr>\n                    </thead>\n                    <tbody>${pagesRows || '<tr><td colspan=\"6\" style=\"text-align:center;\">No page data available</td></tr>'}</tbody>\n                </table>\n            </div>\n        </section>\n\n        <!-- Recommendations -->\n        <section class=\"section\" id=\"recommendations\">\n            <div class=\"section-header\">\n                <div>\n                    <h2 class=\"section-title\">Recommended Actions</h2>\n                    <p class=\"section-subtitle\">Prioritized improvements based on potential impact</p>\n                </div>\n            </div>\n\n            <div class=\"recommendations-list\">\n                ${recsHTML}\n            </div>\n        </section>\n\n        <!-- Footer -->\n        <footer class=\"report-footer\">\n            <div class=\"footer-cta\">Ready to take action? Export your complete report.</div>\n            <button class=\"export-btn\" onclick=\"exportFullReport()\">Download Full Report (PDF)</button>\n            <div style=\"margin-top: 16px;\">\n                <button class=\"export-chart-btn\" onclick=\"exportAllDataCSV()\">Export All Data (Excel)</button>\n            </div>\n            <div class=\"footer-meta\">\n                Report generated on ${dateStr} IST<br>\n                Data source: Google Search Console API | Analysis period: ${analysisPeriod}\n            </div>\n        </footer>\n    </div>\n\n    <script>\n        // =================================================================\n        // INITIALIZATION\n        // =================================================================\n        document.addEventListener('DOMContentLoaded', function () {\n            console.log('Initializing SEO Report...');\n            initTrendChart();\n            initDeviceChart();\n            initRegionBarChart();\n            initCarousel();\n            console.log('All charts initialized successfully');\n        });\n\n        // =================================================================\n        // SMOOTH SCROLL NAVIGATION\n        // =================================================================\n        function scrollToSection(id) {\n            const el = document.getElementById(id);\n            if (el) {\n                el.scrollIntoView({ behavior: 'smooth', block: 'start' });\n            }\n        }\n\n        // =================================================================\n        // REGIONAL CAROUSEL\n        // =================================================================\n        let carouselIndex = 0;\n        let carouselInterval;\n\n        function initCarousel() {\n            startCarousel();\n            const carousel = document.getElementById('regionCarousel');\n            if (carousel) {\n                carousel.addEventListener('mouseenter', () => clearInterval(carouselInterval));\n                carousel.addEventListener('mouseleave', () => startCarousel());\n            }\n        }\n\n        function startCarousel() {\n            const slides = document.querySelectorAll('.carousel-slide');\n            if (slides.length === 0) return;\n            \n            carouselInterval = setInterval(() => {\n                carouselIndex = (carouselIndex + 1) % slides.length;\n                goToSlide(carouselIndex);\n            }, 4000);\n        }\n\n        function goToSlide(index) {\n            carouselIndex = index;\n            document.querySelectorAll('.carousel-slide').forEach((slide, i) => {\n                slide.classList.toggle('active', i === index);\n            });\n            document.querySelectorAll('.carousel-dot').forEach((dot, i) => {\n                dot.classList.toggle('active', i === index);\n            });\n        }\n\n        // =================================================================\n        // TREND CHART\n        // =================================================================\n        function initTrendChart() {\n            const ctx = document.getElementById('trendChart');\n            if (!ctx) return;\n            \n            try {\n                const labels = ${JSON.stringify(trendLabels)};\n                const clicks = ${JSON.stringify(trendClicks)};\n                const impressions = ${JSON.stringify(trendImpressions)};\n\n                new Chart(ctx.getContext('2d'), {\n                    type: 'line',\n                    data: {\n                        labels: labels,\n                        datasets: [\n                            {\n                                label: 'Clicks',\n                                data: clicks,\n                                borderColor: '#1a365d',\n                                backgroundColor: 'rgba(26, 54, 93, 0.1)',\n                                fill: true,\n                                tension: 0.3,\n                                pointRadius: 2,\n                                pointHoverRadius: 6,\n                                pointBackgroundColor: '#1a365d'\n                            },\n                            {\n                                label: 'Impressions (\u00f710)',\n                                data: impressions,\n                                borderColor: '#3182ce',\n                                backgroundColor: 'transparent',\n                                borderDash: [5, 5],\n                                tension: 0.3,\n                                pointRadius: 0,\n                                pointHoverRadius: 4\n                            }\n                        ]\n                    },\n                    options: {\n                        responsive: true,\n                        maintainAspectRatio: false,\n                        interaction: { intersect: false, mode: 'index' },\n                        plugins: {\n                            legend: { position: 'top', align: 'end', labels: { boxWidth: 12, padding: 20, font: { size: 12 } } },\n                            tooltip: {\n                                backgroundColor: '#1a365d',\n                                padding: 12,\n                                callbacks: {\n                                    label: function(context) {\n                                        if (context.datasetIndex === 1) return 'Impressions: ' + (context.raw * 10).toLocaleString();\n                                        return 'Clicks: ' + context.raw.toLocaleString();\n                                    }\n                                }\n                            }\n                        },\n                        scales: {\n                            x: { grid: { display: false }, ticks: { font: { size: 11 }, maxRotation: 45, minRotation: 45 } },\n                            y: { grid: { color: '#e2e8f0' }, ticks: { font: { size: 11 } } }\n                        }\n                    }\n                });\n            } catch (error) { console.error('Trend chart error:', error); }\n        }\n\n        // =================================================================\n        // DEVICE CHART\n        // =================================================================\n        function initDeviceChart() {\n            const ctx = document.getElementById('deviceChart');\n            if (!ctx) return;\n            \n            try {\n                new Chart(ctx.getContext('2d'), {\n                    type: 'doughnut',\n                    data: {\n                        labels: ${JSON.stringify(deviceLabels)},\n                        datasets: [{ data: ${JSON.stringify(deviceDataJSON)}, backgroundColor: ['#1a365d', '#3182ce', '#90cdf4'], borderWidth: 0 }]\n                    },\n                    options: {\n                        responsive: true,\n                        maintainAspectRatio: false,\n                        plugins: {\n                            legend: { position: 'bottom', labels: { padding: 20, font: { size: 12 } } },\n                            tooltip: { backgroundColor: '#1a365d', padding: 12 }\n                        },\n                        cutout: '65%'\n                    }\n                });\n            } catch (error) { console.error('Device chart error:', error); }\n        }\n\n        // =================================================================\n        // REGION BAR CHART\n        // =================================================================\n        function initRegionBarChart() {\n            const ctx = document.getElementById('regionBarChart');\n            if (!ctx) return;\n            \n            try {\n                const data = ${JSON.stringify(regionGrowth)};\n                new Chart(ctx.getContext('2d'), {\n                    type: 'bar',\n                    data: {\n                        labels: ${JSON.stringify(regionLabels)},\n                        datasets: [{ label: 'Growth %', data: data, backgroundColor: data.map(g => g > 0 ? '#276749' : g < 0 ? '#9b2c2c' : '#718096'), borderRadius: 4, barThickness: 16 }]\n                    },\n                    options: {\n                        responsive: true,\n                        maintainAspectRatio: false,\n                        indexAxis: 'y',\n                        plugins: { legend: { display: false }, tooltip: { backgroundColor: '#1a365d', padding: 10 } },\n                        scales: {\n                            x: { grid: { display: false }, ticks: { font: { size: 10 }, callback: v => (v > 0 ? '+' : '') + v + '%' } },\n                            y: { grid: { display: false }, ticks: { font: { size: 11 } } }\n                        }\n                    }\n                });\n            } catch (error) { console.error('Region chart error:', error); }\n        }\n\n        // =================================================================\n        // TABLE UTILITIES\n        // =================================================================\n        function sortTable(tableId, colIndex) {\n            const table = document.getElementById(tableId);\n            if (!table) return;\n            const tbody = table.querySelector('tbody');\n            const rows = Array.from(tbody.querySelectorAll('tr'));\n            const isAsc = table.dataset.sortDir !== 'asc';\n            table.dataset.sortDir = isAsc ? 'asc' : 'desc';\n            rows.sort((a, b) => {\n                let aVal = a.cells[colIndex]?.textContent.replace('Quick Win', '').trim() || '';\n                let bVal = b.cells[colIndex]?.textContent.replace('Quick Win', '').trim() || '';\n                const aNum = parseFloat(aVal.replace(/[^0-9.-]/g, ''));\n                const bNum = parseFloat(bVal.replace(/[^0-9.-]/g, ''));\n                if (!isNaN(aNum) && !isNaN(bNum)) return isAsc ? aNum - bNum : bNum - aNum;\n                return isAsc ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);\n            });\n            rows.forEach(row => tbody.appendChild(row));\n        }\n\n        function filterTable(tableId, query) {\n            const table = document.getElementById(tableId);\n            if (!table) return;\n            const rows = table.querySelectorAll('tbody tr');\n            const q = query.toLowerCase();\n            rows.forEach(row => { row.style.display = row.textContent.toLowerCase().includes(q) ? '' : 'none'; });\n        }\n\n        // =================================================================\n        // EXPORT FUNCTIONS\n        // =================================================================\n        function exportChart(chartId) {\n            const canvas = document.getElementById(chartId);\n            if (!canvas) return alert('Chart not found');\n            const link = document.createElement('a');\n            link.download = chartId + '_' + new Date().toISOString().split('T')[0] + '.png';\n            link.href = canvas.toDataURL('image/png');\n            link.click();\n        }\n\n        function exportTableAsCSV(tableId) {\n            const table = document.getElementById(tableId);\n            if (!table) return alert('Table not found');\n            const rows = table.querySelectorAll('tr');\n            let csv = [];\n            rows.forEach(row => {\n                const cols = row.querySelectorAll('td, th');\n                csv.push(Array.from(cols).map(c => '\"' + c.textContent.replace('Quick Win', '').trim().replace(/\"/g, '\"\"') + '\"').join(','));\n            });\n            const blob = new Blob([csv.join('\\\\n')], { type: 'text/csv' });\n            const link = document.createElement('a');\n            link.download = tableId + '_' + new Date().toISOString().split('T')[0] + '.csv';\n            link.href = URL.createObjectURL(blob);\n            link.click();\n        }\n\n        function exportAllDataCSV() {\n            try {\n                const wb = XLSX.utils.book_new();\n                const summaryData = [['SEO Report - bestwaygujarat.com'], ['Generated', '${dateStr}'], [''], ['Metric', 'Value'], ['Clicks', ${stats.clicks}], ['Impressions', ${stats.impressions}], ['CTR', '${stats.avgCTR}'], ['Position', ${stats.avgPosition}]];\n                wb.SheetNames.push('Summary');\n                wb.Sheets['Summary'] = XLSX.utils.aoa_to_sheet(summaryData);\n                XLSX.writeFile(wb, 'SEO_Report_' + new Date().toISOString().split('T')[0] + '.xlsx');\n            } catch (e) { console.error('Export error:', e); alert('Export failed'); }\n        }\n\n        function exportFullReport() { window.print(); }\n\n        // =================================================================\n        // SMART TOOLTIP POSITIONING\n        // =================================================================\n        document.querySelectorAll('.keyword-cell').forEach(cell => {\n            const tooltip = cell.querySelector('.keyword-tooltip');\n            if (!tooltip) return;\n            \n            cell.addEventListener('mouseenter', (e) => {\n                const rect = cell.getBoundingClientRect();\n                const tooltipHeight = 280;\n                const tooltipWidth = 340;\n                \n                // Position horizontally to the right of keyword\n                let left = rect.right + 10;\n                if (left + tooltipWidth > window.innerWidth) {\n                    left = rect.left - tooltipWidth - 10;\n                }\n                \n                // Position vertically - prefer center, but adjust if clipping\n                let top = rect.top + (rect.height / 2) - (tooltipHeight / 2);\n                if (top < 10) top = 10;\n                if (top + tooltipHeight > window.innerHeight - 10) {\n                    top = window.innerHeight - tooltipHeight - 10;\n                }\n                \n                tooltip.style.left = left + 'px';\n                tooltip.style.top = top + 'px';\n            });\n        });\n    </script>\n</body>\n</html>`;\n\nreturn { json: { htmlReport: htmlReport } };\n                                "
      },
      "id": "43353c3d-f97d-4ce5-bf09-a9eb29d48421",
      "name": "Generate HTML Data",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4144,
        1376
      ]
    },
    {
      "parameters": {
        "respondWith": "text",
        "responseBody": "={{ $json.htmlReport }}",
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "text/html; charset=utf-8"
              }
            ]
          }
        }
      },
      "id": "f2a1fc59-2b47-43b0-b9aa-756054e8d289",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        4432,
        1376
      ]
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        2352,
        1968
      ],
      "id": "877034ab-d303-43e1-8758-d0e05b1deff6",
      "name": "Merge Trends & SearchAPI"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        2352,
        1600
      ],
      "id": "2e99a61f-d2cb-4ecc-a8ca-63008a7da7ec",
      "name": "Merge Sitemaps & Pages"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        2352,
        672
      ],
      "id": "b9d10362-63f6-4c36-a2ed-ff956626944a",
      "name": "Merge Appearance & SERP"
    },
    {
      "parameters": {
        "numberInputs": 5
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        2784,
        1328
      ],
      "id": "36117a23-3cea-47f9-8017-58caddd53c3e",
      "name": "Merge All Data"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://www.googleapis.com/webmasters/v3/sites/{{ encodeURIComponent($('Merge Token').item.json.config.gsc.site) }}/searchAnalytics/query",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $('Merge Token').item.json.gscToken }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"startDate\": \"{{ $('Merge Token').item.json.dates.trend.start }}\",\n  \"endDate\": \"{{ $('Merge Token').item.json.dates.trend.end }}\",\n  \"dimensions\": [\"date\"]\n}",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "id": "e7939fea-e264-4a6c-a12a-866f0d60580a",
      "name": "GSC: Trend",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1504,
        1872
      ]
    },
    {
      "parameters": {
        "content": "## Note \n\nClick on Execute Workflow via Webhook Trigger to run it.\n\nOnce it has started, open the webhook URL in a new tab: https://n8n-service-viri.onrender.com/webhook-test/seo-report to see the report.\n\nIf URL is visited before Execution of workflow it should provide 404 error with: seo-report not registered. Click \"Execute workflow\" first.",
        "height": 320,
        "width": 304
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        256,
        1280
      ],
      "typeVersion": 1,
      "id": "40fcc56a-3f07-4810-a0be-1e3aa82ac585",
      "name": "Sticky Note"
    }
  ],
  "connections": {
    "Run Analysis": {
      "main": [
        [
          {
            "node": "Config + API Keys",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook Trigger": {
      "main": [
        [
          {
            "node": "Config + API Keys",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config + API Keys": {
      "main": [
        [
          {
            "node": "GSC Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GSC Token": {
      "main": [
        [
          {
            "node": "Merge Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Token": {
      "main": [
        [
          {
            "node": "GSC: Keywords",
            "type": "main",
            "index": 0
          },
          {
            "node": "GSC: Pages",
            "type": "main",
            "index": 0
          },
          {
            "node": "GSC: Devices",
            "type": "main",
            "index": 0
          },
          {
            "node": "GSC: Countries",
            "type": "main",
            "index": 0
          },
          {
            "node": "GSC: Trend",
            "type": "main",
            "index": 0
          },
          {
            "node": "GSC: Search Appearance",
            "type": "main",
            "index": 0
          },
          {
            "node": "GSC: Sitemaps",
            "type": "main",
            "index": 0
          },
          {
            "node": "Trends: Niche Interest",
            "type": "main",
            "index": 0
          },
          {
            "node": "Trends: Related Keywords",
            "type": "main",
            "index": 0
          },
          {
            "node": "SERP: Competitors",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GSC: Keywords": {
      "main": [
        [
          {
            "node": "Process Keywords",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GSC: Pages": {
      "main": [
        [
          {
            "node": "Process Pages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GSC: Devices": {
      "main": [
        [
          {
            "node": "Merge Audience",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GSC: Countries": {
      "main": [
        [
          {
            "node": "Merge Audience",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge Audience": {
      "main": [
        [
          {
            "node": "Process Audience",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GSC: Search Appearance": {
      "main": [
        [
          {
            "node": "Process Appearance",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GSC: Sitemaps": {
      "main": [
        [
          {
            "node": "Process Sitemaps",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Trends: Niche Interest": {
      "main": [
        [
          {
            "node": "Merge SearchAPI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Trends: Related Keywords": {
      "main": [
        [
          {
            "node": "Merge SearchAPI",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge SearchAPI": {
      "main": [
        [
          {
            "node": "Process SearchAPI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Keywords": {
      "main": [
        [
          {
            "node": "Merge All Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Pages": {
      "main": [
        [
          {
            "node": "Merge Sitemaps & Pages",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Build AI Prompt": {
      "main": [
        [
          {
            "node": "OpenRouter AI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter AI": {
      "main": [
        [
          {
            "node": "Parse AI Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI Output": {
      "main": [
        [
          {
            "node": "Generate Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Report": {
      "main": [
        [
          {
            "node": "Generate HTML Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate HTML Data": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Trends": {
      "main": [
        [
          {
            "node": "Merge Trends & SearchAPI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process SearchAPI": {
      "main": [
        [
          {
            "node": "Merge Trends & SearchAPI",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Process Sitemaps": {
      "main": [
        [
          {
            "node": "Merge Sitemaps & Pages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Appearance": {
      "main": [
        [
          {
            "node": "Merge Appearance & SERP",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SERP: Competitors": {
      "main": [
        [
          {
            "node": "Merge Appearance & SERP",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge Appearance & SERP": {
      "main": [
        [
          {
            "node": "Merge All Data",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Process Audience": {
      "main": [
        [
          {
            "node": "Merge All Data",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Merge Sitemaps & Pages": {
      "main": [
        [
          {
            "node": "Merge All Data",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "Merge Trends & SearchAPI": {
      "main": [
        [
          {
            "node": "Merge All Data",
            "type": "main",
            "index": 4
          }
        ]
      ]
    },
    "Merge All Data": {
      "main": [
        [
          {
            "node": "Build AI Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GSC: Trend": {
      "main": [
        [
          {
            "node": "Process Trends",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "availableInMCP": false
  },
  "versionId": "727bd5f9-fe2d-4dfc-99a9-8a978421f6e8",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "WR1J27GyflQowHPiCz_fy",
  "tags": []
}