Content Gap Finder

Content Gap Finder

Discover content opportunities your competitors have that you don't.

Content Gap Analysis & Ideas

Your content gap analysis will appear here.
Go to the 'Data Configuration' tab to enter keyword lists.

Great work! No content gaps were found between the two keyword lists.

`; lastGeneratedGaps = null; switchTab(0); return; } generateContentIdeas(gapKeywords); }); async function generateContentIdeas(gapKeywords) { showLoadingState(true); try { const systemPrompt = `You are an expert SEO Content Strategist. For each keyword provided, generate one compelling and actionable blog post title or content idea. Format your response clearly. For each keyword, prefix it with "Keyword:" and the idea with "Idea:". Separate each keyword-idea pair with a blank line.`; const userQuery = `Generate content ideas for the following keywords:\n${gapKeywords.join('\n')}`; const apiKey = ""; const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:generateContent?key=${apiKey}`; const payload = { contents: [{ parts: [{ text: userQuery }] }], systemInstruction: { parts: [{ text: systemPrompt }] }, }; const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) throw new Error(`API error: ${response.statusText}`); const result = await response.json(); const ideasText = result.candidates?.[0]?.content?.parts?.[0]?.text; if (ideasText) { renderGaps(ideasText); showFeedback('Analysis complete!', false); switchTab(0); } else { throw new Error("No content received from API."); } } catch (error) { console.error("Error generating ideas:", error); showFeedback('Failed to generate ideas. Please try again.', true); resultsPreview.innerHTML = `

Could not generate ideas. The AI service may be temporarily unavailable.

`; } finally { showLoadingState(false); } } function parseAndRenderGaps(text) { const pairs = text.split(/\n\s*\n/); // Split by one or more blank lines let html = ''; const structuredGaps = []; pairs.forEach(pair => { const keywordMatch = pair.match(/Keyword:\s*(.*)/); const ideaMatch = pair.match(/Idea:\s*(.*)/); if (keywordMatch && ideaMatch) { const keyword = keywordMatch[1].trim(); const idea = ideaMatch[1].trim(); structuredGaps.push({ keyword, idea }); html += `
${keyword}
${idea}
`; } }); return { structuredGaps, html }; } function renderGaps(text) { const { structuredGaps, html } = parseAndRenderGaps(text); lastGeneratedGaps = structuredGaps; previewPlaceholder.classList.add('hidden'); resultsPreview.innerHTML = html; } function showLoadingState(isLoading) { if (isLoading) { previewPlaceholder.classList.add('hidden'); resultsPreview.innerHTML = '
'; generateBtn.disabled = true; generateBtn.textContent = 'Analyzing...'; showFeedback('Finding gaps and generating ideas...', false, 0); } else { generateBtn.disabled = false; generateBtn.textContent = 'Find Gaps & Generate Ideas'; } } downloadPdfBtn.addEventListener('click', () => { if (!lastGeneratedGaps || lastGeneratedGaps.length === 0) { showFeedback('Please generate an analysis before downloading.', true, 3000); return; } showFeedback('Generating PDF...', false, 2000); try { const { jsPDF } = window.jspdf; const pdf = new jsPDF({ unit: 'pt', format: 'letter' }); const pageMargin = 72; const pageWidth = pdf.internal.pageSize.getWidth(); const textWidth = pageWidth - (pageMargin * 2); let cursorY = pageMargin; // Title pdf.setFont('Helvetica', 'bold'); pdf.setFontSize(20); pdf.text('Content Gap Analysis Report', pageWidth / 2, cursorY, { align: 'center' }); cursorY += 40; lastGeneratedGaps.forEach(gap => { const keywordHeight = pdf.splitTextToSize(gap.keyword, textWidth).length * 16 * 1.2; const ideaHeight = pdf.splitTextToSize(gap.idea, textWidth - 20).length * 12 * 1.2; const totalHeight = keywordHeight + ideaHeight + 20; if (cursorY + totalHeight > pdf.internal.pageSize.getHeight() - pageMargin) { pdf.addPage(); cursorY = pageMargin; } pdf.setFont('Helvetica', 'bold'); pdf.setFontSize(14); const keywordLines = pdf.splitTextToSize(gap.keyword, textWidth); pdf.text(keywordLines, pageMargin, cursorY); cursorY += keywordLines.length * 14 * 1.2 + 5; pdf.setFont('Helvetica', 'normal'); pdf.setFontSize(11); const ideaLines = pdf.splitTextToSize(gap.idea, textWidth - 20); pdf.text(ideaLines, pageMargin + 20, cursorY); cursorY += ideaLines.length * 11 * 1.2 + 25; // Add extra space between items }); pdf.save('content-gap-analysis.pdf'); } catch(err) { console.error("PDF Generation Error: ", err); showFeedback("Error generating PDF.", true, 3000); } }); let feedbackTimeout; function showFeedback(message, isError = false, duration = 3000) { clearTimeout(feedbackTimeout); feedbackMessage.innerHTML = message; feedbackMessage.style.color = isError ? '#ef4444' : '#4a5568'; if (duration !== 0) { // allow indefinite message feedbackTimeout = setTimeout(() => { feedbackMessage.innerHTML = ''; }, duration); } } });
Scroll to Top