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'); } }; // Import Google Trends API functions import { fetchGoogleTrends, getTrendingDate } from './trends-api.js'; // Fetch trending topics from multiple sources for diversity const fetchTrendingTopics = async () => { try { let combinedTopics = []; // Source 1: Google Trends API console.log('Fetching trending topics from Google Trends...'); const trendingData = await fetchGoogleTrends(); if (trendingData && trendingData.length > 0) { combinedTopics = [...combinedTopics, ...trendingData]; } // Update trending date display updateTrendingDateDisplay(); // Source 2: Wikipedia trending articles (as backup) 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(); // 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 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 []; } }; // Update trending date display in the UI const updateTrendingDateDisplay = () => { const dateInfo = getTrendingDate(); const dateDisplay = document.getElementById('trending-date'); if (dateDisplay) { const formattedDate = new Date(dateInfo.timestamp).toLocaleString(); dateDisplay.textContent = `Trending as of: ${formattedDate}`; dateDisplay.style.display = 'block'; } }; // 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 SR (Search Relevance) - how relevant the keyword is based on search volume const searchRelevance = Math.round(normalizedSearchVolume); // Calculate DR (Difficulty Rating) - how difficult it is to rank for this keyword // Lower result count means easier to rank (lower difficulty) const difficultyRating = Math.round(100 - normalizedResultCount); // Calculate opportunity score (weighted average) // 60% weight to search volume, 40% weight to inverse result count const opportunityScore = Math.round((normalizedSearchVolume * 0.6) + (normalizedResultCount * 0.4)); // Calculate competition level (lower is better) const competitionLevel = Math.round(keyword.resultCount / keyword.searchVolume); // Calculate keyword quality score (0-100) for color coding // Higher is better - combines all metrics const qualityScore = Math.round( (opportunityScore * 0.5) + (searchRelevance * 0.3) + ((100 - difficultyRating) * 0.2) ); return { ...keyword, opportunityScore, searchRelevance, difficultyRating, competitionLevel, qualityScore }; }); // 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'; // Determine color based on quality score const qualityScore = keyword.qualityScore || score; const qualityColor = getQualityColor(qualityScore); keywordCard.innerHTML = `
${keyword.keyword}
Opportunity: ${score}
Search: ${searchVolume}
Results: ${resultCount}
Competition: ${competitionLevel}
${keyword.searchRelevance ? `
SR: ${keyword.searchRelevance}
` : ''} ${keyword.difficultyRating ? `
DR: ${keyword.difficultyRating}
` : ''}
`; keywordCard.addEventListener('click', () => selectKeyword(keyword.keyword, keywordCard)); keywordsContainer.appendChild(keywordCard); }); }; // Get color based on quality score const getQualityColor = (score) => { // Green gradient for good keywords (score > 70) if (score > 85) return '#00a651'; // Bright green if (score > 70) return '#4caf50'; // Green // Yellow/Orange for medium quality keywords (score 50-70) if (score > 60) return '#8bc34a'; // Light green if (score > 50) return '#ffeb3b'; // Yellow // Red gradient for poor keywords (score < 50) if (score > 40) return '#ffc107'; // Amber if (score > 30) return '#ff9800'; // Orange return '#f44336'; // Red }; // Select a keyword const selectKeyword = (keyword, card) => { // Remove selected class from all cards document.querySelectorAll('.keyword-card').forEach(k => k.classList.remove('selected')); // Add selected class to clicked card card.classList.add('selected'); // Update selected keyword selectedKeyword = keyword; selectedKeywordText.textContent = keyword; // Enable generate prompt button generatePromptBtn.disabled = false; // Hide prompt result if visible promptResult.classList.add('hidden'); }; // Generate article prompt const generatePrompt = () => { if (!selectedKeyword) return; try { loading.classList.remove('hidden'); generatePromptBtn.disabled = true; // Find the selected keyword data const keywordData = Array.from(document.querySelectorAll('.keyword-card')).find( card => card.dataset.keyword === selectedKeyword ); // Get opportunity metrics if available const opportunityScore = keywordData?.querySelector('.opportunity-score .highlight')?.textContent || 'high'; const searchVolume = keywordData?.querySelector('.search-volume')?.textContent.split(': ')[1] || 'significant'; const resultCount = keywordData?.querySelector('.result-count')?.textContent.split(': ')[1] || 'limited'; // Generate a ChatGPT-friendly prompt based on the keyword and opportunity metrics const prompt = { title: `Write a comprehensive article about "${selectedKeyword}" (High-Opportunity Keyword)`, content: `Write a well-researched, engaging, and informative article about "${selectedKeyword}". This is a high-opportunity keyword with ${searchVolume} monthly searches but only ${resultCount} competing results. 1. Start with an attention-grabbing introduction that explains why "${selectedKeyword}" is important or relevant today 2. Include at least 5 main sections with appropriate headings 3. Incorporate current statistics, trends, and expert opinions 4. Address common questions or misconceptions about "${selectedKeyword}" 5. Provide practical tips, applications, or future predictions related to "${selectedKeyword}" 6. End with a compelling conclusion that summarizes key points and offers final thoughts 7. Optimize the content for SEO while maintaining high-quality, valuable information The article should be approximately 1500-2000 words, written in a professional yet accessible tone, and formatted for easy online reading with appropriate headings, subheadings, and bullet points where relevant.` }; currentPrompt = prompt; displayPrompt(prompt); } catch (error) { console.error('Error generating prompt:', error); alert('Failed to generate prompt. Please try again.'); } finally { loading.classList.add('hidden'); generatePromptBtn.disabled = false; } }; // Display the generated prompt const displayPrompt = (prompt) => { promptTitle.textContent = prompt.title; promptBody.textContent = prompt.content; promptResult.classList.remove('hidden'); }; // Copy prompt to clipboard const copyPrompt = () => { if (!currentPrompt) return; const fullPrompt = `${currentPrompt.title}\n\n${currentPrompt.content}`; navigator.clipboard.writeText(fullPrompt) .then(() => { copySuccess.classList.remove('hidden'); setTimeout(() => { copySuccess.classList.add('hidden'); }, 2000); }) .catch(err => { console.error('Failed to copy prompt:', err); alert('Failed to copy prompt to clipboard'); }); }; // Event listeners refreshBtn.addEventListener('click', fetchTrendingKeywords); generatePromptBtn.addEventListener('click', generatePrompt); copyPromptBtn.addEventListener('click', copyPrompt); // Initial load fetchTrendingKeywords(); });