GEO Optimizer

Audit your website's visibility to ChatGPT, Perplexity, Claude & Gemini

stars 📦 /mo 🔍 audits
Connecting...
0 / 100
Trend
Baseline snapshot saved for this URL.

    Why this tool exists

    AI search is already deciding which websites get cited and which don't. The signals are documented, the research is published — but most of the web has no idea. GEO Optimizer exists to make AI visibility auditable, transparent, and accessible to every developer, not just teams with enterprise budgets.

    Auditability Scientific Foundation Open Source Precision
    Read the full manifesto →
    /* ─── Cookie Consent Management ─── */ (function() { 'use strict'; const COOKIE_CONSENT = 'cookie_consent'; const GA_ID = 'G-KV5MQTGFZG'; const DEFAULT_CONSENT = { essential: true, analytics: false, marketing: false }; const banner = document.getElementById('cookie-banner'); const modal = document.getElementById('cookie-modal'); const overlay = document.getElementById('cookie-modal-overlay'); let consent = loadConsent(); let consentGiven = hasStoredConsent(); function loadConsent() { try { const s = localStorage.getItem(COOKIE_CONSENT); if (s) return { ...DEFAULT_CONSENT, ...JSON.parse(s) }; const c = document.cookie.split(';').find(c => c.trim().startsWith(COOKIE_CONSENT + '=')); if (c) return { ...DEFAULT_CONSENT, ...JSON.parse(decodeURIComponent(c.split('=')[1])) }; } catch(e) { console.warn('Cookie consent load error', e); } return { ...DEFAULT_CONSENT }; } function hasStoredConsent() { try { if (localStorage.getItem(COOKIE_CONSENT)) return true; if (document.cookie.split(';').some(c => c.trim().startsWith(COOKIE_CONSENT + '='))) return true; } catch(e) {} return false; } function saveConsent(obj) { try { localStorage.setItem(COOKIE_CONSENT, JSON.stringify(obj)); const exp = new Date(); exp.setFullYear(exp.getFullYear() + 1); var secure = location.protocol === 'https:' ? ';Secure' : ''; document.cookie = COOKIE_CONSENT + '=' + encodeURIComponent(JSON.stringify(obj)) + ';expires=' + exp.toUTCString() + ';path=/;SameSite=Lax' + secure; } catch(e) { console.warn('Cookie consent save error', e); } } function showBanner() { banner.style.display = 'flex'; } function hideBanner() { banner.style.display = 'none'; } function openModal() { document.getElementById('cookie-essential').checked = true; document.getElementById('cookie-analytics').checked = consent.analytics; document.getElementById('cookie-marketing').checked = consent.marketing; modal.hidden = false; setTimeout(function() { modal.classList.add('active'); if (overlay) overlay.style.opacity = '1'; document.getElementById('cookie-analytics').focus(); }, 10); } function closeModal() { modal.classList.remove('active'); if (overlay) overlay.style.opacity = '0'; setTimeout(function() { modal.hidden = true; }, 300); } function acceptAll() { consent = { essential: true, analytics: true, marketing: true }; saveConsent(consent); consentGiven = true; hideBanner(); loadGA(); } function rejectAll() { consent = { essential: true, analytics: false, marketing: false }; saveConsent(consent); consentGiven = true; closeModal(); hideBanner(); disableTracking(); } function savePreferences() { consent = { essential: true, analytics: document.getElementById('cookie-analytics').checked, marketing: document.getElementById('cookie-marketing').checked }; saveConsent(consent); consentGiven = true; closeModal(); hideBanner(); if (consent.analytics) loadGA(); else disableTracking(); } function loadGA() { if (!consent.analytics) return; window.dataLayer = window.dataLayer || []; function gtag() { dataLayer.push(arguments); } gtag('js', new Date()); gtag('config', GA_ID, { anonymize_ip: true }); var s = document.createElement('script'); s.src = 'https://www.googletagmanager.com/gtag/js?id=' + GA_ID; s.async = true; document.head.appendChild(s); } function disableTracking() { var s = document.querySelector('script[src*="googletagmanager.com"]'); if (s) s.remove(); document.cookie.split(';').forEach(function(c) { var n = c.trim().split('=')[0]; if (n.startsWith('_ga') || n.startsWith('_gcl')) { document.cookie = n + '=;expires=Thu,01 Jan 1970 00:00:00 GMT;path=/;domain=' + location.hostname; document.cookie = n + '=;expires=Thu,01 Jan 1970 00:00:00 GMT;path=/;domain=.' + location.hostname; } }); } function init() { // Attach listeners always (regardless of prior consent) document.getElementById('cookie-accept-all').addEventListener('click', acceptAll); document.getElementById('cookie-manage').addEventListener('click', openModal); document.getElementById('cookie-reject-all').addEventListener('click', rejectAll); document.getElementById('cookie-save').addEventListener('click', savePreferences); document.getElementById('cookie-modal-close').addEventListener('click', closeModal); if (overlay) overlay.addEventListener('click', closeModal); document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && !modal.hidden) closeModal(); }); if (consentGiven) { hideBanner(); loadGA(); } else { showBanner(); } } // Expose globally for floating button window.openCookieModal = openModal; init(); })(); /* ─── Constants ─── */ const BANDS = {excellent:'#22c55e',good:'#06b6d4',foundation:'#eab308',critical:'#ef4444'}; const CATEGORIES = [ {key:'robots',label:'Robots.txt',max:18}, {key:'llms',label:'llms.txt',max:18}, {key:'schema',label:'Schema JSON-LD',max:16}, {key:'meta',label:'Meta Tags',max:14}, {key:'content',label:'Content',max:12}, {key:'signals',label:'Signals',max:6}, {key:'ai_discovery',label:'AI Discovery',max:6}, {key:'brand_entity',label:'Brand & Entity',max:10}, ]; const SPIN_MSGS = ['Checking robots.txt...','Analyzing llms.txt...','Parsing schema...','Evaluating content...','Scoring citability...','Compiling results...']; /* ─── Version + Stats ─── */ (async function init(){ try{ const h = await fetch('/health'); if(h.ok){const d=await h.json();var vb=document.getElementById('nav-ver');if(vb)vb.textContent='v'+d.version;} }catch(e){} try{ const s = await fetch('/api/stats'); if(s.ok){ const d=await s.json(); const fmt=n=>n>=1000?(n/1000).toFixed(1)+'k':String(n); document.getElementById('stars').textContent=fmt(d.github_stars); document.getElementById('downloads').textContent=fmt(d.pypi_downloads_month); document.getElementById('audits').textContent=fmt(d.audits_run); var ns=document.getElementById('nav-stars');if(ns&&d.github_stars)ns.textContent='★ '+d.github_stars; } }catch(e){} })(); /* ─── Audit ─── */ const $=id=>document.getElementById(id); let spinInterval; function startSpin(){ $('spinner').classList.add('active'); let i=0; $('spin-text').textContent=SPIN_MSGS[0]; spinInterval=setInterval(()=>{i=(i+1)%SPIN_MSGS.length;$('spin-text').textContent=SPIN_MSGS[i]},2200); } function stopSpin(){$('spinner').classList.remove('active');clearInterval(spinInterval)} function isValidUrl(str){ /* Accetta URL con protocollo, oppure dominio semplice (es. example.com) */ if(/^https?:\/\/.+\..+/.test(str))return true; /* Dominio senza protocollo: almeno un punto, niente spazi */ if(/^[^\s]+\.[a-z]{2,}(\/.*)?$/i.test(str))return true; return false; } async function runAudit(){ const input=$('url-input'); const hint=$('url-hint'); let url=input.value.trim(); if(!url){hint.style.display='none';return} /* Validazione lato client */ if(!isValidUrl(url)){ hint.textContent='Please enter a valid URL (e.g. https://example.com)'; hint.style.display='block'; input.focus(); return; } hint.style.display='none'; /* Aggiungi https:// se manca il protocollo */ if(!/^https?:\/\//i.test(url))url='https://'+url; input.value=url; $('btn').disabled=true;$('error').style.display='none';$('result').style.display='none'; startSpin(); try{ const res=await fetch('/api/audit?url='+encodeURIComponent(url)); const data=await res.json(); if(!res.ok)throw new Error(data.detail||'Audit failed'); renderResult(data,url); }catch(e){ $('error').textContent=e.message;$('error').style.display='block'; }finally{stopSpin();$('btn').disabled=false} } function renderResult(data,url){ const color=BANDS[data.band]||'#888'; const score=data.score||0; /* Gauge */ const circ=314.16; const offset=circ-(score/100*circ); const fill=$('gauge-fill'); fill.style.stroke=color; fill.style.strokeDashoffset=String(offset); $('gauge-score').textContent=score; $('gauge-score').style.fill=color; const tag=$('band-tag'); tag.textContent=data.band?data.band.toUpperCase():'—'; tag.style.background=color+'1a';tag.style.color=color; /* Breakdown */ const bd=$('breakdown');bd.textContent=''; const br=data.score_breakdown||{}; for(const cat of CATEGORIES){ const val=br[cat.key]||0; const pct=Math.min(val/cat.max*100,100); const row=document.createElement('div');row.className='bar-row'; const lbl=document.createElement('span');lbl.className='bar-label';lbl.textContent=cat.label; const track=document.createElement('div');track.className='bar-track'; const barFill=document.createElement('div');barFill.className='bar-fill'; barFill.style.width='0%';barFill.style.background=color; track.appendChild(barFill); const valSpan=document.createElement('span');valSpan.className='bar-val'; valSpan.textContent=val+'/'+cat.max; row.appendChild(lbl);row.appendChild(track);row.appendChild(valSpan); bd.appendChild(row); requestAnimationFrame(()=>{requestAnimationFrame(()=>{barFill.style.width=pct+'%'})}); } /* Citability */ const cit=data.citability; if(cit&&cit.total_score!==undefined){ $('cit-wrap').style.display='flex'; const cs=$('cit-score');cs.textContent=cit.total_score+'/100'; const cc=cit.total_score>=86?BANDS.excellent:cit.total_score>=68?BANDS.good:cit.total_score>=36?BANDS.foundation:BANDS.critical; cs.style.color=cc; $('cit-grade').textContent=cit.grade?cit.grade.toUpperCase():''; $('cit-grade').style.color=cc; }else{$('cit-wrap').style.display='none'} /* Recommendations */ const recs=data.recommendations||[]; $('recs-count').textContent='('+recs.length+')'; const rl=$('recs-list');rl.textContent='';rl.classList.remove('open'); $('recs-toggle').classList.remove('open'); for(const r of recs){const li=document.createElement('li');li.textContent=r;rl.appendChild(li)} /* Trend */ const history=data.history; const trendBox=$('trend-box'); if(history && history.total_snapshots){ trendBox.style.display='block'; const scoreDelta=history.score_delta; const latestBand=(history.latest_band||'critical').toUpperCase(); let summary=`Current baseline: ${history.latest_score}/100 (${latestBand}).`; if(history.total_snapshots >= 2 && scoreDelta !== null && scoreDelta !== undefined){ if(scoreDelta > 0) summary=`Improved from ${history.previous_score} to ${history.latest_score} since the previous snapshot.`; else if(scoreDelta < 0) summary=`Dropped from ${history.previous_score} to ${history.latest_score} since the previous snapshot.`; else summary=`Stable at ${history.latest_score}/100 since the previous snapshot.`; } $('trend-summary').textContent=summary; const deltaNode=$('trend-delta'); if(scoreDelta === null || scoreDelta === undefined){ deltaNode.textContent='BASE'; deltaNode.style.color='var(--text-dim)'; }else{ deltaNode.textContent=(scoreDelta > 0 ? '+' : '') + scoreDelta; deltaNode.style.color=scoreDelta >= 0 ? BANDS.good : BANDS.critical; } const trendList=$('trend-list');trendList.textContent=''; (history.entries||[]).slice(0,5).forEach(item=>{ const row=document.createElement('div');row.className='trend-row'; const date=document.createElement('span');date.className='trend-date';date.textContent=(item.timestamp||'').slice(0,10); const score=document.createElement('span');score.className='trend-score'; const delta=(item.delta === null || item.delta === undefined) ? '—' : ((item.delta > 0 ? '+' : '') + item.delta); score.textContent=`${item.score}/100 · ${delta}`; const bar=document.createElement('div');bar.className='trend-bar'; const fill=document.createElement('span');fill.className='trend-fill'; fill.style.width=Math.max(4, Math.min(100, item.score||0))+'%'; bar.appendChild(fill); row.appendChild(date);row.appendChild(score);row.appendChild(bar); trendList.appendChild(row); }); }else{ trendBox.style.display='none'; } /* Actions */ const acts=$('actions');acts.textContent=''; if(data.report_url){ const a=document.createElement('a');a.className='action-link';a.href=data.report_url; a.target='_blank';a.rel='noopener';a.textContent='HTML Report';acts.appendChild(a); } const pdf=document.createElement('a');pdf.className='action-link'; pdf.href='/api/audit/pdf?url='+encodeURIComponent(url); pdf.textContent='Download PDF';acts.appendChild(pdf); $('result').style.display='block'; } /* ─── Event listeners ─── */ $('btn').addEventListener('click',runAudit); $('url-input').addEventListener('keypress',e=>{if(e.key==='Enter')runAudit()}); $('recs-toggle').addEventListener('click',function(){ this.classList.toggle('open');$('recs-list').classList.toggle('open'); }); $('copy-btn').addEventListener('click',function(){ navigator.clipboard.writeText('pip install geo-optimizer-skill').then(()=>{this.textContent='Done!'}); setTimeout(()=>{this.textContent='\u{1F4CB}'},1500); }); document.getElementById('nav-ham').addEventListener('click',function(){ document.querySelector('.nav-links').classList.toggle('open'); this.textContent=this.textContent==='☰'?'✕':'☰'; }); document.querySelectorAll('.nav-dropdown-toggle').forEach(function(btn){ btn.addEventListener('click',function(e){ e.stopPropagation(); var dd=this.closest('.nav-dropdown'); dd.classList.toggle('open'); this.setAttribute('aria-expanded',dd.classList.contains('open')); }); }); document.addEventListener('click',function(){ document.querySelectorAll('.nav-dropdown.open').forEach(function(dd){ dd.classList.remove('open'); dd.querySelector('.nav-dropdown-toggle').setAttribute('aria-expanded','false'); }); }); (function() { var floatBtn = document.getElementById('cookie-float-btn'); var banner = document.getElementById('cookie-banner'); // Mostra il pulsante solo quando il banner non è visibile function updateFloatBtnVisibility() { if (!floatBtn) return; var bannerVisible = banner && banner.style.display !== 'none' && !banner.hidden; floatBtn.style.display = bannerVisible ? 'none' : 'flex'; } // Controlla dopo breve delay (il banner usa display/hidden) setTimeout(updateFloatBtnVisibility, 500); // Osserva cambiamenti al banner if (banner && window.MutationObserver) { var obs = new MutationObserver(updateFloatBtnVisibility); obs.observe(banner, { attributes: true, attributeFilter: ['style', 'hidden'] }); } // Al click apre il modal cookie se disponibile if (floatBtn) { floatBtn.addEventListener('click', function() { if (typeof window.openCookieModal === 'function') { window.openCookieModal(); } }); } })();