Analyzes all Search Console data to provide keyword and SEO opportunities to increase traffic, CTR and impressions
Size
35.1 KB
Version
1.0.1
Created
Mar 3, 2026
Updated
19 days ago
1// ==UserScript==
2// @name Search Console SEO Analyzer
3// @description Analyzes all Search Console data to provide keyword and SEO opportunities to increase traffic, CTR and impressions
4// @version 1.0.1
5// @match https://search.google.com/search-console/*
6// @icon https://www.gstatic.com/search-console/scfe/favicon.png
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('Search Console SEO Analyzer initialized');
12
13 // Utility function to wait for element
14 function waitForElement(selector, timeout = 10000) {
15 return new Promise((resolve, reject) => {
16 const element = document.querySelector(selector);
17 if (element) {
18 return resolve(element);
19 }
20
21 const observer = new MutationObserver((mutations, obs) => {
22 const element = document.querySelector(selector);
23 if (element) {
24 obs.disconnect();
25 resolve(element);
26 }
27 });
28
29 observer.observe(document.body, {
30 childList: true,
31 subtree: true
32 });
33
34 setTimeout(() => {
35 observer.disconnect();
36 reject(new Error(`Element ${selector} not found within ${timeout}ms`));
37 }, timeout);
38 });
39 }
40
41 // Debounce function for MutationObserver
42 function debounce(func, wait) {
43 let timeout;
44 return function executedFunction(...args) {
45 const later = () => {
46 clearTimeout(timeout);
47 func(...args);
48 };
49 clearTimeout(timeout);
50 timeout = setTimeout(later, wait);
51 };
52 }
53
54 // Extract data from the current Search Console page
55 async function extractSearchConsoleData() {
56 console.log('Extracting Search Console data...');
57 const data = {
58 queries: [],
59 pages: [],
60 countries: [],
61 devices: [],
62 searchAppearance: [],
63 dates: [],
64 totalClicks: 0,
65 totalImpressions: 0,
66 avgCTR: 0,
67 avgPosition: 0
68 };
69
70 try {
71 // Extract summary metrics from scorecards
72 const scorecards = document.querySelectorAll('.PCt8xf.ZzqKLb.IKe2ed');
73 scorecards.forEach(card => {
74 const label = card.getAttribute('data-label');
75 const valueElement = card.querySelector('.nnLLaf.vtZz6e');
76 if (valueElement) {
77 const value = valueElement.textContent.trim().replace(/\s/g, '').replace(/\./g, '').replace(',', '.');
78 const numValue = parseFloat(value);
79
80 if (label === 'CLICKS') data.totalClicks = numValue;
81 if (label === 'IMPRESSIONS') data.totalImpressions = numValue;
82 if (label === 'CTR') data.avgCTR = numValue;
83 if (label === 'POSITION') data.avgPosition = numValue;
84 }
85 });
86
87 console.log('Summary metrics extracted:', {
88 clicks: data.totalClicks,
89 impressions: data.totalImpressions,
90 ctr: data.avgCTR,
91 position: data.avgPosition
92 });
93
94 // Extract table data (queries, pages, etc.)
95 await extractTableData(data);
96
97 } catch (error) {
98 console.error('Error extracting Search Console data:', error);
99 }
100
101 return data;
102 }
103
104 // Extract data from the performance table
105 async function extractTableData(data) {
106 console.log('Extracting table data...');
107
108 // Wait for table to load
109 await new Promise(resolve => setTimeout(resolve, 2000));
110
111 const tableRows = document.querySelectorAll('table tbody tr, .yMNAbc, [role="row"]');
112 console.log(`Found ${tableRows.length} table rows`);
113
114 tableRows.forEach((row, index) => {
115 try {
116 const cells = row.querySelectorAll('td, [role="cell"]');
117 if (cells.length >= 4) {
118 const queryText = cells[0]?.textContent?.trim();
119 const clicks = parseFloat(cells[1]?.textContent?.trim().replace(/\./g, '').replace(',', '.')) || 0;
120 const impressions = parseFloat(cells[2]?.textContent?.trim().replace(/\./g, '').replace(',', '.')) || 0;
121 const ctr = parseFloat(cells[3]?.textContent?.trim().replace('%', '').replace(',', '.')) || 0;
122 const position = parseFloat(cells[4]?.textContent?.trim().replace(',', '.')) || 0;
123
124 if (queryText && queryText.length > 0 && !queryText.includes('Consulta')) {
125 data.queries.push({
126 query: queryText,
127 clicks: clicks,
128 impressions: impressions,
129 ctr: ctr,
130 position: position
131 });
132 }
133 }
134 } catch (error) {
135 console.error(`Error parsing row ${index}:`, error);
136 }
137 });
138
139 console.log(`Extracted ${data.queries.length} queries from table`);
140 }
141
142 // Navigate through different tabs and collect data
143 async function collectAllData(progressCallback) {
144 console.log('Starting comprehensive data collection...');
145 const allData = {
146 queries: [],
147 pages: [],
148 summary: {}
149 };
150
151 try {
152 // Extract current page data
153 progressCallback('Extracting performance data...');
154 const performanceData = await extractSearchConsoleData();
155 allData.summary = {
156 totalClicks: performanceData.totalClicks,
157 totalImpressions: performanceData.totalImpressions,
158 avgCTR: performanceData.avgCTR,
159 avgPosition: performanceData.avgPosition
160 };
161 allData.queries = performanceData.queries;
162
163 // Try to load more rows if pagination exists
164 progressCallback('Loading all available data...');
165 await loadMoreRows();
166
167 // Re-extract after loading more
168 const updatedData = await extractSearchConsoleData();
169 allData.queries = updatedData.queries;
170
171 console.log('Data collection complete:', {
172 queriesCount: allData.queries.length,
173 summary: allData.summary
174 });
175
176 } catch (error) {
177 console.error('Error collecting data:', error);
178 }
179
180 return allData;
181 }
182
183 // Try to load more rows in the table
184 async function loadMoreRows() {
185 console.log('Attempting to load more rows...');
186
187 // Look for "Show more" or pagination buttons
188 const showMoreButtons = document.querySelectorAll('button, [role="button"]');
189
190 for (const button of showMoreButtons) {
191 const buttonText = button.textContent.toLowerCase();
192 if (buttonText.includes('mรกs') || buttonText.includes('more') || buttonText.includes('cargar')) {
193 console.log('Found load more button, clicking...');
194 button.click();
195 await new Promise(resolve => setTimeout(resolve, 2000));
196 break;
197 }
198 }
199
200 // Try to change rows per page to maximum
201 const rowsPerPageSelects = document.querySelectorAll('select, [role="listbox"]');
202 for (const select of rowsPerPageSelects) {
203 if (select.options && select.options.length > 0) {
204 select.value = select.options[select.options.length - 1].value;
205 select.dispatchEvent(new Event('change', { bubbles: true }));
206 await new Promise(resolve => setTimeout(resolve, 2000));
207 break;
208 }
209 }
210 }
211
212 // Analyze data with AI to provide SEO opportunities
213 async function analyzeWithAI(data, progressCallback) {
214 console.log('Starting AI analysis...');
215 progressCallback('Analyzing data with AI...');
216
217 try {
218 const prompt = `You are an SEO expert analyzing Google Search Console data.
219
220SUMMARY METRICS:
221- Total Clicks: ${data.summary.totalClicks}
222- Total Impressions: ${data.summary.totalImpressions}
223- Average CTR: ${data.summary.avgCTR}%
224- Average Position: ${data.summary.avgPosition}
225
226TOP QUERIES DATA (${data.queries.length} queries):
227${JSON.stringify(data.queries.slice(0, 50), null, 2)}
228
229Analyze this data and provide actionable SEO recommendations to increase traffic, CTR, and impressions. Focus on:
2301. High-impression, low-CTR keywords (quick wins)
2312. Keywords ranking positions 4-20 (opportunity to reach page 1)
2323. Content gaps and keyword opportunities
2334. Technical SEO recommendations
2345. Priority actions ranked by potential impact`;
235
236 const analysis = await RM.aiCall(prompt, {
237 type: "json_schema",
238 json_schema: {
239 name: "seo_analysis",
240 schema: {
241 type: "object",
242 properties: {
243 quickWins: {
244 type: "array",
245 items: {
246 type: "object",
247 properties: {
248 keyword: { type: "string" },
249 currentCTR: { type: "number" },
250 currentPosition: { type: "number" },
251 impressions: { type: "number" },
252 recommendation: { type: "string" },
253 potentialImpact: { type: "string" }
254 },
255 required: ["keyword", "recommendation", "potentialImpact"]
256 }
257 },
258 rankingOpportunities: {
259 type: "array",
260 items: {
261 type: "object",
262 properties: {
263 keyword: { type: "string" },
264 currentPosition: { type: "number" },
265 targetPosition: { type: "number" },
266 recommendation: { type: "string" },
267 estimatedTrafficIncrease: { type: "string" }
268 },
269 required: ["keyword", "currentPosition", "recommendation"]
270 }
271 },
272 contentGaps: {
273 type: "array",
274 items: {
275 type: "object",
276 properties: {
277 topic: { type: "string" },
278 suggestedKeywords: { type: "array", items: { type: "string" } },
279 reasoning: { type: "string" }
280 },
281 required: ["topic", "reasoning"]
282 }
283 },
284 technicalRecommendations: {
285 type: "array",
286 items: {
287 type: "object",
288 properties: {
289 issue: { type: "string" },
290 solution: { type: "string" },
291 priority: { type: "string", enum: ["high", "medium", "low"] }
292 },
293 required: ["issue", "solution", "priority"]
294 }
295 },
296 priorityActions: {
297 type: "array",
298 items: {
299 type: "object",
300 properties: {
301 action: { type: "string" },
302 impact: { type: "string", enum: ["high", "medium", "low"] },
303 effort: { type: "string", enum: ["high", "medium", "low"] },
304 description: { type: "string" }
305 },
306 required: ["action", "impact", "effort", "description"]
307 }
308 },
309 overallSummary: { type: "string" }
310 },
311 required: ["quickWins", "rankingOpportunities", "priorityActions", "overallSummary"]
312 }
313 }
314 });
315
316 console.log('AI analysis complete:', analysis);
317 return analysis;
318
319 } catch (error) {
320 console.error('Error during AI analysis:', error);
321 throw error;
322 }
323 }
324
325 // Display results in a modal
326 function displayResults(analysis, data) {
327 console.log('Displaying results...');
328
329 // Remove existing modal if any
330 const existingModal = document.getElementById('seo-analyzer-modal');
331 if (existingModal) {
332 existingModal.remove();
333 }
334
335 // Create modal
336 const modal = document.createElement('div');
337 modal.id = 'seo-analyzer-modal';
338 modal.innerHTML = `
339 <div class="seo-modal-overlay">
340 <div class="seo-modal-content">
341 <div class="seo-modal-header">
342 <h2>๐ SEO Analysis Results</h2>
343 <button class="seo-modal-close" id="seo-close-modal">ร</button>
344 </div>
345 <div class="seo-modal-body">
346 <div class="seo-summary-section">
347 <h3>๐ Summary</h3>
348 <p>${analysis.overallSummary}</p>
349 <div class="seo-metrics">
350 <div class="seo-metric">
351 <span class="metric-label">Total Clicks</span>
352 <span class="metric-value">${data.summary.totalClicks.toLocaleString()}</span>
353 </div>
354 <div class="seo-metric">
355 <span class="metric-label">Total Impressions</span>
356 <span class="metric-value">${data.summary.totalImpressions.toLocaleString()}</span>
357 </div>
358 <div class="seo-metric">
359 <span class="metric-label">Avg CTR</span>
360 <span class="metric-value">${data.summary.avgCTR}%</span>
361 </div>
362 <div class="seo-metric">
363 <span class="metric-label">Avg Position</span>
364 <span class="metric-value">${data.summary.avgPosition.toFixed(1)}</span>
365 </div>
366 </div>
367 </div>
368
369 <div class="seo-section">
370 <h3>โก Quick Wins (${analysis.quickWins.length})</h3>
371 <p class="section-description">High-impression keywords with low CTR - optimize these for immediate impact</p>
372 ${analysis.quickWins.map(item => `
373 <div class="seo-item">
374 <div class="seo-item-header">
375 <strong>${item.keyword}</strong>
376 <span class="impact-badge impact-${item.potentialImpact?.toLowerCase() || 'medium'}">${item.potentialImpact || 'Medium Impact'}</span>
377 </div>
378 <div class="seo-item-stats">
379 ${item.currentPosition ? `<span>Position: ${item.currentPosition}</span>` : ''}
380 ${item.currentCTR ? `<span>CTR: ${item.currentCTR}%</span>` : ''}
381 ${item.impressions ? `<span>Impressions: ${item.impressions.toLocaleString()}</span>` : ''}
382 </div>
383 <p>${item.recommendation}</p>
384 </div>
385 `).join('')}
386 </div>
387
388 <div class="seo-section">
389 <h3>๐ Ranking Opportunities (${analysis.rankingOpportunities.length})</h3>
390 <p class="section-description">Keywords close to page 1 - push these to top positions</p>
391 ${analysis.rankingOpportunities.map(item => `
392 <div class="seo-item">
393 <div class="seo-item-header">
394 <strong>${item.keyword}</strong>
395 <span class="position-badge">Position ${item.currentPosition} โ ${item.targetPosition || 'Top 3'}</span>
396 </div>
397 ${item.estimatedTrafficIncrease ? `<div class="traffic-estimate">๐ ${item.estimatedTrafficIncrease}</div>` : ''}
398 <p>${item.recommendation}</p>
399 </div>
400 `).join('')}
401 </div>
402
403 ${analysis.contentGaps && analysis.contentGaps.length > 0 ? `
404 <div class="seo-section">
405 <h3>๐ Content Gaps</h3>
406 <p class="section-description">Missing content opportunities based on your current performance</p>
407 ${analysis.contentGaps.map(item => `
408 <div class="seo-item">
409 <div class="seo-item-header">
410 <strong>${item.topic}</strong>
411 </div>
412 ${item.suggestedKeywords && item.suggestedKeywords.length > 0 ? `
413 <div class="keyword-tags">
414 ${item.suggestedKeywords.map(kw => `<span class="keyword-tag">${kw}</span>`).join('')}
415 </div>
416 ` : ''}
417 <p>${item.reasoning}</p>
418 </div>
419 `).join('')}
420 </div>
421 ` : ''}
422
423 ${analysis.technicalRecommendations && analysis.technicalRecommendations.length > 0 ? `
424 <div class="seo-section">
425 <h3>๐ง Technical Recommendations</h3>
426 ${analysis.technicalRecommendations.map(item => `
427 <div class="seo-item">
428 <div class="seo-item-header">
429 <strong>${item.issue}</strong>
430 <span class="priority-badge priority-${item.priority}">${item.priority.toUpperCase()}</span>
431 </div>
432 <p>${item.solution}</p>
433 </div>
434 `).join('')}
435 </div>
436 ` : ''}
437
438 <div class="seo-section">
439 <h3>๐ฏ Priority Actions</h3>
440 <p class="section-description">Recommended actions ranked by impact vs effort</p>
441 ${analysis.priorityActions.map(item => `
442 <div class="seo-item priority-action">
443 <div class="seo-item-header">
444 <strong>${item.action}</strong>
445 <div class="action-badges">
446 <span class="impact-badge impact-${item.impact}">Impact: ${item.impact}</span>
447 <span class="effort-badge effort-${item.effort}">Effort: ${item.effort}</span>
448 </div>
449 </div>
450 <p>${item.description}</p>
451 </div>
452 `).join('')}
453 </div>
454
455 <div class="seo-export-section">
456 <button class="seo-export-btn" id="seo-export-results">๐ฅ Export Results as JSON</button>
457 <button class="seo-copy-btn" id="seo-copy-summary">๐ Copy Summary</button>
458 </div>
459 </div>
460 </div>
461 </div>
462 `;
463
464 document.body.appendChild(modal);
465
466 // Add event listeners
467 document.getElementById('seo-close-modal').addEventListener('click', () => {
468 modal.remove();
469 });
470
471 document.getElementById('seo-export-results').addEventListener('click', async () => {
472 const exportData = {
473 analysis: analysis,
474 data: data,
475 exportDate: new Date().toISOString()
476 };
477 const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
478 const url = URL.createObjectURL(blob);
479 const a = document.createElement('a');
480 a.href = url;
481 a.download = `seo-analysis-${new Date().toISOString().split('T')[0]}.json`;
482 a.click();
483 URL.revokeObjectURL(url);
484 });
485
486 document.getElementById('seo-copy-summary').addEventListener('click', async () => {
487 const summary = `SEO Analysis Summary\n\n${analysis.overallSummary}\n\nQuick Wins: ${analysis.quickWins.length}\nRanking Opportunities: ${analysis.rankingOpportunities.length}\nPriority Actions: ${analysis.priorityActions.length}`;
488 await GM.setClipboard(summary);
489 alert('Summary copied to clipboard!');
490 });
491
492 // Close on overlay click
493 modal.querySelector('.seo-modal-overlay').addEventListener('click', (e) => {
494 if (e.target.classList.contains('seo-modal-overlay')) {
495 modal.remove();
496 }
497 });
498 }
499
500 // Show progress modal
501 function showProgressModal(message) {
502 let progressModal = document.getElementById('seo-progress-modal');
503
504 if (!progressModal) {
505 progressModal = document.createElement('div');
506 progressModal.id = 'seo-progress-modal';
507 progressModal.innerHTML = `
508 <div class="seo-modal-overlay">
509 <div class="seo-progress-content">
510 <div class="seo-spinner"></div>
511 <h3 id="seo-progress-message">Analyzing...</h3>
512 <p class="seo-progress-subtitle">This may take a minute...</p>
513 </div>
514 </div>
515 `;
516 document.body.appendChild(progressModal);
517 }
518
519 document.getElementById('seo-progress-message').textContent = message;
520 return progressModal;
521 }
522
523 function hideProgressModal() {
524 const progressModal = document.getElementById('seo-progress-modal');
525 if (progressModal) {
526 progressModal.remove();
527 }
528 }
529
530 // Main analysis function
531 async function runAnalysis() {
532 console.log('Starting SEO analysis...');
533
534 try {
535 const progressModal = showProgressModal('Collecting Search Console data...');
536
537 // Collect all data
538 const data = await collectAllData((message) => {
539 document.getElementById('seo-progress-message').textContent = message;
540 });
541
542 if (data.queries.length === 0) {
543 hideProgressModal();
544 alert('No query data found. Please make sure you are on the Performance page with data visible.');
545 return;
546 }
547
548 // Analyze with AI
549 document.getElementById('seo-progress-message').textContent = 'Analyzing with AI...';
550 const analysis = await analyzeWithAI(data, (message) => {
551 document.getElementById('seo-progress-message').textContent = message;
552 });
553
554 hideProgressModal();
555
556 // Display results
557 displayResults(analysis, data);
558
559 // Save results
560 await GM.setValue('last_seo_analysis', JSON.stringify({
561 analysis: analysis,
562 data: data,
563 timestamp: new Date().toISOString()
564 }));
565
566 } catch (error) {
567 console.error('Error running analysis:', error);
568 hideProgressModal();
569 alert('Error running analysis: ' + error.message);
570 }
571 }
572
573 // Add styles
574 const styles = `
575 .seo-analyzer-button {
576 display: inline-flex;
577 align-items: center;
578 gap: 8px;
579 padding: 10px 20px;
580 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
581 color: white;
582 border: none;
583 border-radius: 8px;
584 font-size: 14px;
585 font-weight: 500;
586 cursor: pointer;
587 box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
588 transition: all 0.3s ease;
589 margin: 16px 0;
590 }
591
592 .seo-analyzer-button:hover {
593 transform: translateY(-2px);
594 box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
595 }
596
597 .seo-analyzer-button:active {
598 transform: translateY(0);
599 }
600
601 .seo-modal-overlay {
602 position: fixed;
603 top: 0;
604 left: 0;
605 right: 0;
606 bottom: 0;
607 background: rgba(0, 0, 0, 0.7);
608 display: flex;
609 align-items: center;
610 justify-content: center;
611 z-index: 999999;
612 padding: 20px;
613 }
614
615 .seo-modal-content {
616 background: white;
617 border-radius: 12px;
618 max-width: 900px;
619 width: 100%;
620 max-height: 90vh;
621 display: flex;
622 flex-direction: column;
623 box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
624 }
625
626 .seo-modal-header {
627 display: flex;
628 justify-content: space-between;
629 align-items: center;
630 padding: 24px;
631 border-bottom: 1px solid #e0e0e0;
632 }
633
634 .seo-modal-header h2 {
635 margin: 0;
636 font-size: 24px;
637 color: #333;
638 }
639
640 .seo-modal-close {
641 background: none;
642 border: none;
643 font-size: 32px;
644 color: #666;
645 cursor: pointer;
646 padding: 0;
647 width: 32px;
648 height: 32px;
649 display: flex;
650 align-items: center;
651 justify-content: center;
652 border-radius: 4px;
653 transition: all 0.2s;
654 }
655
656 .seo-modal-close:hover {
657 background: #f0f0f0;
658 color: #333;
659 }
660
661 .seo-modal-body {
662 padding: 24px;
663 overflow-y: auto;
664 }
665
666 .seo-summary-section {
667 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
668 color: white;
669 padding: 24px;
670 border-radius: 8px;
671 margin-bottom: 24px;
672 }
673
674 .seo-summary-section h3 {
675 margin: 0 0 12px 0;
676 font-size: 20px;
677 }
678
679 .seo-summary-section p {
680 margin: 0 0 16px 0;
681 line-height: 1.6;
682 }
683
684 .seo-metrics {
685 display: grid;
686 grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
687 gap: 16px;
688 }
689
690 .seo-metric {
691 background: rgba(255, 255, 255, 0.2);
692 padding: 12px;
693 border-radius: 6px;
694 display: flex;
695 flex-direction: column;
696 gap: 4px;
697 }
698
699 .metric-label {
700 font-size: 12px;
701 opacity: 0.9;
702 }
703
704 .metric-value {
705 font-size: 24px;
706 font-weight: bold;
707 }
708
709 .seo-section {
710 margin-bottom: 32px;
711 }
712
713 .seo-section h3 {
714 font-size: 20px;
715 color: #333;
716 margin: 0 0 8px 0;
717 }
718
719 .section-description {
720 color: #666;
721 font-size: 14px;
722 margin: 0 0 16px 0;
723 }
724
725 .seo-item {
726 background: #f8f9fa;
727 border-left: 4px solid #667eea;
728 padding: 16px;
729 margin-bottom: 12px;
730 border-radius: 4px;
731 }
732
733 .seo-item-header {
734 display: flex;
735 justify-content: space-between;
736 align-items: center;
737 margin-bottom: 8px;
738 flex-wrap: wrap;
739 gap: 8px;
740 }
741
742 .seo-item-header strong {
743 color: #333;
744 font-size: 16px;
745 }
746
747 .seo-item-stats {
748 display: flex;
749 gap: 16px;
750 margin-bottom: 8px;
751 font-size: 13px;
752 color: #666;
753 }
754
755 .seo-item p {
756 margin: 0;
757 color: #555;
758 line-height: 1.6;
759 }
760
761 .impact-badge, .priority-badge, .position-badge {
762 padding: 4px 12px;
763 border-radius: 12px;
764 font-size: 12px;
765 font-weight: 600;
766 text-transform: uppercase;
767 }
768
769 .impact-high, .priority-high {
770 background: #ff4444;
771 color: white;
772 }
773
774 .impact-medium, .priority-medium {
775 background: #ffaa00;
776 color: white;
777 }
778
779 .impact-low, .priority-low {
780 background: #00cc66;
781 color: white;
782 }
783
784 .position-badge {
785 background: #667eea;
786 color: white;
787 }
788
789 .action-badges {
790 display: flex;
791 gap: 8px;
792 }
793
794 .effort-badge {
795 padding: 4px 12px;
796 border-radius: 12px;
797 font-size: 12px;
798 font-weight: 600;
799 text-transform: uppercase;
800 }
801
802 .effort-high {
803 background: #ff6b6b;
804 color: white;
805 }
806
807 .effort-medium {
808 background: #ffd93d;
809 color: #333;
810 }
811
812 .effort-low {
813 background: #6bcf7f;
814 color: white;
815 }
816
817 .traffic-estimate {
818 background: #e3f2fd;
819 color: #1976d2;
820 padding: 8px 12px;
821 border-radius: 4px;
822 font-size: 13px;
823 margin-bottom: 8px;
824 }
825
826 .keyword-tags {
827 display: flex;
828 flex-wrap: wrap;
829 gap: 8px;
830 margin-bottom: 8px;
831 }
832
833 .keyword-tag {
834 background: #e0e7ff;
835 color: #4c51bf;
836 padding: 4px 10px;
837 border-radius: 4px;
838 font-size: 12px;
839 }
840
841 .seo-export-section {
842 display: flex;
843 gap: 12px;
844 margin-top: 24px;
845 padding-top: 24px;
846 border-top: 1px solid #e0e0e0;
847 }
848
849 .seo-export-btn, .seo-copy-btn {
850 flex: 1;
851 padding: 12px 24px;
852 border: none;
853 border-radius: 6px;
854 font-size: 14px;
855 font-weight: 500;
856 cursor: pointer;
857 transition: all 0.2s;
858 }
859
860 .seo-export-btn {
861 background: #667eea;
862 color: white;
863 }
864
865 .seo-export-btn:hover {
866 background: #5568d3;
867 }
868
869 .seo-copy-btn {
870 background: #f0f0f0;
871 color: #333;
872 }
873
874 .seo-copy-btn:hover {
875 background: #e0e0e0;
876 }
877
878 .seo-progress-content {
879 background: white;
880 padding: 40px;
881 border-radius: 12px;
882 text-align: center;
883 min-width: 300px;
884 }
885
886 .seo-spinner {
887 width: 50px;
888 height: 50px;
889 border: 4px solid #f3f3f3;
890 border-top: 4px solid #667eea;
891 border-radius: 50%;
892 animation: spin 1s linear infinite;
893 margin: 0 auto 20px;
894 }
895
896 @keyframes spin {
897 0% { transform: rotate(0deg); }
898 100% { transform: rotate(360deg); }
899 }
900
901 .seo-progress-content h3 {
902 margin: 0 0 8px 0;
903 color: #333;
904 }
905
906 .seo-progress-subtitle {
907 margin: 0;
908 color: #666;
909 font-size: 14px;
910 }
911
912 .priority-action {
913 border-left-color: #ff6b6b;
914 }
915 `;
916
917 TM_addStyle(styles);
918
919 // Initialize the button
920 async function init() {
921 console.log('Initializing SEO Analyzer button...');
922
923 try {
924 // Wait for the navigation element
925 const navElement = await waitForElement('.cp8g2d', 15000);
926 console.log('Navigation element found');
927
928 // Check if button already exists
929 if (document.getElementById('seo-analyzer-btn-container')) {
930 console.log('Button already exists');
931 return;
932 }
933
934 // Create button container
935 const buttonContainer = document.createElement('div');
936 buttonContainer.id = 'seo-analyzer-btn-container';
937 buttonContainer.style.cssText = 'padding: 0 24px;';
938
939 const button = document.createElement('button');
940 button.className = 'seo-analyzer-button';
941 button.innerHTML = '๐ Analyze SEO Opportunities';
942 button.addEventListener('click', runAnalysis);
943
944 buttonContainer.appendChild(button);
945
946 // Insert before the navigation element
947 navElement.parentElement.insertBefore(buttonContainer, navElement);
948 console.log('SEO Analyzer button added successfully');
949
950 } catch (error) {
951 console.error('Error initializing SEO Analyzer:', error);
952 }
953 }
954
955 // Run initialization when DOM is ready
956 if (document.readyState === 'loading') {
957 document.addEventListener('DOMContentLoaded', init);
958 } else {
959 init();
960 }
961
962 // Re-initialize on navigation changes (for SPA behavior)
963 const debouncedInit = debounce(init, 1000);
964 const observer = new MutationObserver(debouncedInit);
965 observer.observe(document.body, {
966 childList: true,
967 subtree: true
968 });
969
970})();