← Audit Tool
Roadmap
Research
Compare
Docs
Compare GEO Scores
Side-by-side comparison — see who's more visible to AI.
vs
Compare
Analyzing both sites... this may take up to 60 seconds.
document.getElementById('btn').addEventListener('click', runCompare); document.getElementById('url2').addEventListener('keypress', function(e) { if (e.key === 'Enter') runCompare(); }); async function runCompare() { const url1 = document.getElementById('url1').value; const url2 = document.getElementById('url2').value; if (!url1 || !url2) return; const btn = document.getElementById('btn'); const spinner = document.getElementById('spinner'); const errorEl = document.getElementById('error'); const resultsEl = document.getElementById('results'); btn.disabled = true; spinner.classList.add('active'); errorEl.style.display = 'none'; resultsEl.style.display = 'none'; try { const [res1, res2] = await Promise.all([ fetch('/api/audit?url=' + encodeURIComponent(url1)), fetch('/api/audit?url=' + encodeURIComponent(url2)) ]); const [data1, data2] = await Promise.all([res1.json(), res2.json()]); if (!res1.ok) throw new Error('Site A: ' + (data1.detail || 'Error')); if (!res2.ok) throw new Error('Site B: ' + (data2.detail || 'Error')); renderComparison(data1, data2); resultsEl.style.display = 'block'; } catch (e) { errorEl.textContent = e.message; errorEl.style.display = 'block'; } finally { btn.disabled = false; spinner.classList.remove('active'); } } function renderComparison(a, b) { const colors = {excellent:'#22c55e',good:'#06b6d4',foundation:'#eab308',critical:'#ef4444'}; const comp = document.getElementById('comparison'); comp.textContent = ''; [a, b].forEach((data, i) => { const isWinner = (i === 0 ? a.score >= b.score : b.score >= a.score) && a.score !== b.score; const card = document.createElement('div'); card.className = 'site-card' + (isWinner ? ' winner' : ''); const color = colors[data.band] || '#888'; const url = document.createElement('div'); url.className = 'site-url'; url.textContent = data.url || (i === 0 ? document.getElementById('url1').value : document.getElementById('url2').value); card.appendChild(url); const score = document.createElement('div'); score.className = 'site-score'; score.style.color = color; score.textContent = data.score + '/100'; card.appendChild(score); const band = document.createElement('div'); band.className = 'site-band'; band.style.color = color; band.textContent = data.band ? data.band.toUpperCase() : ''; card.appendChild(band); const breakdown = document.createElement('div'); breakdown.className = 'breakdown'; const categories = {robots_txt:'Robots',llms_txt:'llms.txt',schema_jsonld:'Schema',meta_tags:'Meta',content:'Content'}; const maxes = {robots_txt:18,llms_txt:18,schema_jsonld:22,meta_tags:14,content:14}; for (const [key, label] of Object.entries(categories)) { const c = data.checks ? data.checks[key] : null; const sc = breakdown[key] !== undefined ? breakdown[key] : (c ? (c.score || 0) : 0); const mx = maxes[key]; const pct = Math.min(sc / mx * 100, 100); const row = document.createElement('div'); row.className = 'bar-row'; // Fix #47: usa DOM API sicure const labelSpan = document.createElement('span'); labelSpan.className = 'bar-label'; labelSpan.textContent = label; const barBg = document.createElement('div'); barBg.className = 'bar-bg'; const barFill = document.createElement('div'); barFill.className = 'bar-fill'; barFill.style.width = pct + '%'; barFill.style.background = color; barBg.appendChild(barFill); const valSpan = document.createElement('span'); valSpan.className = 'bar-val'; valSpan.textContent = sc; row.appendChild(labelSpan); row.appendChild(barBg); row.appendChild(valSpan); '
' + '
' + sc + '
'; breakdown.appendChild(row); } card.appendChild(breakdown); comp.appendChild(card); }); }