document.addEventListener('DOMContentLoaded', () => { // DOM Elements const keywordsContainer = document.getElementById('keywords-container'); const refreshBtn = document.getElementById('refresh-btn'); const loading = document.getElementById('loading'); const selectedKeywordText = document.getElementById('selected-keyword-text'); const generatePromptBtn = document.getElementById('generate-prompt-btn'); const promptResult = document.getElementById('prompt-result'); const promptTitle = document.getElementById('prompt-title'); const promptBody = document.getElementById('prompt-body'); const copyPromptBtn = document.getElementById('copy-prompt-btn'); const copySuccess = document.getElementById('copy-success'); // State let selectedKeyword = null; let currentPrompt = null; // Fetch trending keywords with opportunity scoring const fetchTrendingKeywords = async () => { try { loading.classList.remove('hidden'); keywordsContainer.innerHTML = ''; // Step 1: Get trending topics from multiple sources const trendingTopics = await fetchTrendingTopics(); // Step 2: Enrich with search volume and result count data const enrichedKeywords = await enrichKeywordsWithOpportunityData(trendingTopics); // Step 3: Calculate opportunity score and sort const keywordsWithOpportunity = calculateOpportunityScore(enrichedKeywords); if (keywordsWithOpportunity.length > 0) { renderKeywords(keywordsWithOpportunity); } else { useBackupKeywords(); } } catch (error) { console.error('Error fetching trending keywords:', error); useBackupKeywords(); } finally { loading.classList.add('hidden'); } }; // Fetch trending topics from multiple sources for diversity const fetchTrendingTopics = async () => { try { // Source 1: Wikipedia trending articles const wikiResponse = await fetch('https://wikimedia.org/api/rest_v1/metrics/pageviews/top/en.wikipedia/all-access/2023/01/all-days'); const wikiData = await wikiResponse.json(); let combinedTopics = []; // Process Wikipedia data if (wikiData && wikiData.items && wikiData.items[0] && wikiData.items[0].articles) { const wikiTopics = wikiData.items[0].articles .filter(article => !article.article.startsWith('Special:') && !article.article.startsWith('Main_Page') && !article.article.startsWith('Wikipedia:')) .slice(0, 15) .map(article => ({ keyword: article.article.replace(/_/g, ' '), searchVolume: article.views, source: 'wikipedia' })); combinedTopics = [...combinedTopics, ...wikiTopics]; } // Source 2: Use Google Trends API-like data (simulated) // In a real implementation, you would use Google Trends API const techTrendingTopics = [ { keyword: 'quantum computing applications', searchVolume: 85000 }, { keyword: 'edge computing use cases', searchVolume: 62000 }, { keyword: 'zero-knowledge proofs', searchVolume: 45000 }, { keyword: 'synthetic data generation', searchVolume: 73000 }, { keyword: 'federated learning models', searchVolume: 58000 }, { keyword: 'serverless architecture patterns', searchVolume: 67000 }, { keyword: 'homomorphic encryption', searchVolume: 41000 }, { keyword: 'computer vision in healthcare', searchVolume: 89000 }, { keyword: 'explainable ai techniques', searchVolume: 76000 }, { keyword: 'graph neural networks', searchVolume: 52000 } ].map(item => ({ ...item, source: 'tech_trends' })); combinedTopics = [...combinedTopics, ...techTrendingTopics]; // Source 3: Emerging research topics (simulated) const emergingTopics = [ { keyword: 'biomimetic materials science', searchVolume: 32000 }, { keyword: 'neuromorphic computing chips', searchVolume: 28000 }, { keyword: 'digital twin technology applications', searchVolume: 47000 }, { keyword: 'post-quantum cryptography standards', searchVolume: 39000 }, { keyword: 'spatial computing interfaces', searchVolume: 43000 } ].map(item => ({ ...item, source: 'emerging_research' })); combinedTopics = [...combinedTopics, ...emergingTopics]; // Deduplicate and return return Array.from(new Map(combinedTopics.map(item => [item.keyword, item])).values()); } catch (error) { console.error('Error fetching trending topics:', error); return []; } }; // Enrich keywords with search result counts const enrichKeywordsWithOpportunityData = async (keywords) => { // In a production environment, you would use a real search API // Here we'll simulate the data with a deterministic algorithm return Promise.all(keywords.map(async (keyword) => { // Simulate API call to get search result count // In reality, you would use a search engine API // Algorithm to generate realistic but varied result counts // Technical/specific keywords tend to have fewer results const wordCount = keyword.keyword.split(' ').length; const containsTechnicalTerms = /\b(api|algorithm|framework|protocol|quantum|neural|encryption|serverless|computing)\b/i.test(keyword.keyword); const isNiche = wordCount >= 3 || containsTechnicalTerms; // Base result count - more specific terms have fewer results let baseResultCount; if (isNiche) { baseResultCount = Math.floor(50000 + Math.random() * 500000); } else { baseResultCount = Math.floor(1000000 + Math.random() * 50000000); } // Add some randomness but keep it deterministic for the same keyword const seed = keyword.keyword.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0); const pseudoRandom = Math.sin(seed) * 10000; const resultCount = Math.max(10000, Math.floor(baseResultCount + pseudoRandom)); return { ...keyword, resultCount }; })); }; // Calculate opportunity score based on search volume vs. result count const calculateOpportunityScore = (keywords) => { // Calculate the opportunity score // Higher score = high search volume + low result count (better opportunity) const keywordsWithScore = keywords.map(keyword => { // Normalize search volume (0-100) const maxSearchVolume = Math.max(...keywords.map(k => k.searchVolume)); const normalizedSearchVolume = (keyword.searchVolume / maxSearchVolume) * 100; // Normalize result count inversely (fewer results = higher score) const maxResultCount = Math.max(...keywords.map(k => k.resultCount)); const normalizedResultCount = 100 - ((keyword.resultCount / maxResultCount) * 100); // Calculate opportunity score (weighted average) // 60% weight to search volume, 40% weight to inverse result count const opportunityScore = (normalizedSearchVolume * 0.6) + (normalizedResultCount * 0.4); // Calculate competition level (lower is better) const competitionLevel = keyword.resultCount / keyword.searchVolume; return { ...keyword, opportunityScore: Math.round(opportunityScore), competitionLevel: Math.round(competitionLevel) }; }); // Sort by opportunity score (highest first) return keywordsWithScore .sort((a, b) => b.opportunityScore - a.opportunityScore) .slice(0, 10); }; // Use backup keywords when API fails const useBackupKeywords = () => { const backupKeywords = [ { keyword: 'quantum machine learning algorithms', opportunityScore: 95, searchVolume: 42000, resultCount: 156000, competitionLevel: 4 }, { keyword: 'zero-trust network architecture', opportunityScore: 92, searchVolume: 68000, resultCount: 310000, competitionLevel: 5 }, { keyword: 'synthetic data generation techniques', opportunityScore: 89, searchVolume: 51000, resultCount: 245000, competitionLevel: 5 }, { keyword: 'edge computing security frameworks', opportunityScore: 87, searchVolume: 73000, resultCount: 420000, competitionLevel: 6 }, { keyword: 'federated learning privacy', opportunityScore: 85, searchVolume: 47000, resultCount: 280000, competitionLevel: 6 }, { keyword: 'explainable ai for healthcare', opportunityScore: 82, searchVolume: 59000, resultCount: 390000, competitionLevel: 7 }, { keyword: 'post-quantum cryptography implementation', opportunityScore: 80, searchVolume: 38000, resultCount: 265000, competitionLevel: 7 }, { keyword: 'neuromorphic computing applications', opportunityScore: 78, searchVolume: 31000, resultCount: 225000, competitionLevel: 7 }, { keyword: 'graph neural networks for recommendation', opportunityScore: 76, searchVolume: 44000, resultCount: 340000, competitionLevel: 8 }, { keyword: 'digital twin technology standards', opportunityScore: 74, searchVolume: 53000, resultCount: 420000, competitionLevel: 8 } ]; renderKeywords(backupKeywords); }; // Render keywords const renderKeywords = (keywords) => { keywordsContainer.innerHTML = ''; keywords.forEach(keyword => { const keywordCard = document.createElement('div'); keywordCard.className = 'keyword-card'; keywordCard.dataset.keyword = keyword.keyword; // Display opportunity score and metrics const score = keyword.opportunityScore || keyword.score || 0; const searchVolume = keyword.searchVolume ? `${(keyword.searchVolume/1000).toFixed(1)}K` : 'N/A'; const resultCount = keyword.resultCount ? `${(keyword.resultCount/1000).toFixed(1)}K` : 'N/A'; const competitionLevel = keyword.competitionLevel || 'N/A'; keywordCard.innerHTML = `