JSCode

// AI Visibility Audit – Main JavaScript (CORS-Free Version)
// Configuration
const CONFIG = {
googleApiKey: ‘AIzaSyAlXfpY2Y7k_1EU2oWSHT6wplFZYzrQ9wQ’,
web3formsKey: ‘0c6d9fe3-e589-4e41-9d18-276360df2ece’
};

// PageSpeed Insights API helper
class PageSpeedAPI {
constructor(apiKey) {
this.apiKey = apiKey;
this.apiUrl = ‘https://www.googleapis.com/pagespeedonline/v5/runPagespeed’;
}

async analyze(url) {
try {
const response = await fetch(
`${this.apiUrl}?url=${encodeURIComponent(url)}&key=${this.apiKey}&category=SEO&category=PERFORMANCE`
);

const data = await response.json();

if (data.lighthouseResult) {
return this.parseResults(data.lighthouseResult);
}

return null;
} catch (error) {
console.error(‘PageSpeed API error:’, error);
return null;
}
}

parseResults(lighthouse) {
const audits = lighthouse.audits;
const finalUrl = lighthouse.finalUrl;

const titleAudit = audits[‘document-title’];
const metaDescAudit = audits[‘meta-description’];
const structuredDataAudit = audits[‘structured-data’];

const structuredDataItems = structuredDataAudit?.details?.items || [];

return {
title: titleAudit?.displayValue || titleAudit?.title || null,
metaDescription: metaDescAudit?.displayValue || metaDescAudit?.description || null,
hasValidTitle: titleAudit?.score === 1,
hasValidMetaDesc: metaDescAudit?.score === 1,

structuredData: structuredDataItems,
hasStructuredData: structuredDataItems.length > 0,
structuredDataScore: structuredDataAudit?.score,

isMobileFriendly: audits[‘viewport’]?.score === 1,
isHTTPS: finalUrl?.startsWith(‘https://’),

hasValidHreflang: audits[‘hreflang’]?.score !== 0,
isIndexable: audits[‘is-crawlable’]?.score === 1 || audits[‘is-crawlable’]?.score === null,
hasValidCanonical: audits[‘canonical’]?.score !== 0,

performanceScore: Math.round((lighthouse.categories?.performance?.score || 0) * 100),
seoScore: Math.round((lighthouse.categories?.seo?.score || 0) * 100),

_debug: {
titleAudit: titleAudit,
metaDescAudit: metaDescAudit,
structuredDataAudit: structuredDataAudit
}
};
}
}

// Audit engine
class VisibilityAuditor {
constructor(url, businessName) {
this.url = url;
this.businessName = businessName;
this.results = {
seo: [],
local: [],
ai: [],
social: []
};
this.pageSpeed = new PageSpeedAPI(CONFIG.googleApiKey);
this.pageSpeedData = null;
}

async runAudit() {
this.pageSpeedData = await this.pageSpeed.analyze(this.url);

await Promise.all([
this.checkSEO(),
this.checkLocal(),
this.checkAI(),
this.checkSocial()
]);
return this.calculateScores();
}

async checkSEO() {
const checks = [];

if (this.pageSpeedData) {
console.log(‘PageSpeed Data:’, this.pageSpeedData._debug);

let html = null;
try {
console.log(‘Fetching HTML for backup checks…’);
const response = await fetch(`https://api.allorigins.win/raw?url=${encodeURIComponent(this.url)}`);
html = await response.text();
console.log(‘HTML fetched successfully, length:’, html.length);
} catch (error) {
console.error(‘Failed to fetch HTML for backup checks:’, error);
}

checks.push({
title: ‘Secure Connection (HTTPS)’,
passed: this.pageSpeedData.isHTTPS,
detail: this.pageSpeedData.isHTTPS
? ‘Site uses secure HTTPS protocol’
: ‘Site should use HTTPS for security and SEO’,
verified: true
});

const titleDetails = this.pageSpeedData._debug.titleAudit;
const titleText = titleDetails?.displayValue ||
titleDetails?.description ||
(titleDetails?.score === 1 ? ‘Title tag found’ : null);

checks.push({
title: ‘Title Tag Present & Optimized’,
passed: this.pageSpeedData.hasValidTitle,
detail: titleText || ‘Missing or invalid title tag – critical for SEO’,
verified: true
});

const metaDetails = this.pageSpeedData._debug.metaDescAudit;
const metaText = metaDetails?.displayValue ||
metaDetails?.description ||
(metaDetails?.score === 1 ? ‘Meta description found’ : null);

checks.push({
title: ‘Meta Description’,
passed: this.pageSpeedData.hasValidMetaDesc,
detail: metaText || ‘Missing meta description’,
verified: true
});

const schemaDetails = this.pageSpeedData._debug.structuredDataAudit;
let schemaCount = this.pageSpeedData.structuredData.length;
let hasStructuredData = this.pageSpeedData.hasStructuredData;

if (!hasStructuredData && html) {
console.log(‘Running backup schema check…’);
const jsonLdMatches = html.match(/]*type=[“‘]application\/ld\+json[“‘][^>]*>/gi);
const hasSchemaOrg = html.includes(‘schema.org’);

if (jsonLdMatches || hasSchemaOrg) {
hasStructuredData = true;
schemaCount = jsonLdMatches ? jsonLdMatches.length : 1;
console.log(‘Backup schema check: FOUND’, schemaCount, ‘schemas’);
} else {
console.log(‘Backup schema check: NOT FOUND’);
}
}

checks.push({
title: ‘Structured Data (Schema)’,
passed: hasStructuredData,
detail: hasStructuredData
? `Found ${schemaCount} schema type(s)`
: schemaDetails?.description || ‘No structured data found – limits AI understanding’,
verified: true
});

let isMobileFriendly = this.pageSpeedData.isMobileFriendly;

if (!isMobileFriendly && html) {
console.log(‘Running backup viewport check…’);
isMobileFriendly = html.includes(‘name=”viewport”‘) || html.includes(“name=’viewport'”);
console.log(‘Backup viewport check:’, isMobileFriendly ? ‘FOUND’ : ‘NOT FOUND’);
}

checks.push({
title: ‘Mobile-Friendly’,
passed: isMobileFriendly,
detail: isMobileFriendly
? ‘Page is mobile-friendly (viewport meta tag present)’
: ‘Page may not be optimized for mobile devices – add viewport meta tag’,
verified: true
});

checks.push({
title: ‘Search Engine Indexable’,
passed: this.pageSpeedData.isIndexable,
detail: this.pageSpeedData.isIndexable
? ‘Page is crawlable by search engines’
: ‘Page may be blocking search engine crawlers (check robots.txt)’,
verified: true
});

} else {
checks.push({
title: ‘SEO Analysis Unavailable’,
passed: null,
detail: ‘Unable to complete automated SEO analysis. Please contact us for a manual review.’,
verified: false
});
}

this.results.seo = checks;
}

async checkLocal() {
const checks = [];

try {
const response = await fetch(`https://api.allorigins.win/raw?url=${encodeURIComponent(this.url)}`);
const html = await response.text();

// Check for contact information on page
const hasPhone = /(\+?1[-.\s]?)?\(?([0-9]{3})\)?[-.\s]?([0-9]{3})[-.\s]?([0-9]{4})/i.test(html);
const hasEmail = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/i.test(html);
const hasAddress = /\b\d+\s+\w+\s+(street|st|avenue|ave|road|rd|drive|dr|lane|ln|court|ct|circle|cir)\b/i.test(html);
const hasBusinessName = html.toLowerCase().includes(this.businessName.toLowerCase()) || html.includes(‘business’) || html.includes(‘company’);

checks.push({
title: ‘Business Name on Website’,
passed: hasBusinessName,
detail: hasBusinessName
? ‘Business name found on your website’
: ‘Business name not prominently displayed – add it to improve local SEO’,
verified: true
});

checks.push({
title: ‘Contact Information (Phone/Email)’,
passed: hasPhone || hasEmail,
detail: hasPhone && hasEmail
? ‘Phone and email found on website’
: hasPhone || hasEmail
? ‘Phone or email found – consider adding both’
: ‘No phone or email found – critical for local SEO’,
verified: true
});

checks.push({
title: ‘Physical Address on Website’,
passed: hasAddress,
detail: hasAddress
? ‘Physical address found on website’
: ‘No physical address detected – add your business address for local SEO’,
verified: true
});

const hasReviewMentions = html.includes(‘review’) || html.includes(‘testimonial’) || html.includes(‘rating’);
checks.push({
title: ‘Review/Testimonial Mentions’,
passed: hasReviewMentions,
detail: hasReviewMentions
? ‘Review or testimonial language found on site’
: ‘No review/testimonial references – encourage customers to leave reviews on Google’,
verified: true
});

} catch (error) {
checks.push({
title: ‘Local Search Information’,
passed: null,
detail: ‘Unable to analyze local search factors. Recommendation: Ensure your Google Business Profile is claimed and complete with phone, hours, address, and recent reviews.’,
verified: false
});
}

this.results.local = checks;
}

async checkAI() {
const checks = [];

if (this.pageSpeedData) {
let structuredDataTypes = this.pageSpeedData.structuredData.map(item => item[‘@type’] || ‘Unknown’);
let hasFAQ = structuredDataTypes.some(type => type.includes(‘FAQ’) || type.includes(‘Question’));
let hasOrg = structuredDataTypes.some(type => type.includes(‘Organization’) || type.includes(‘LocalBusiness’) || type.includes(‘ProfessionalService’));

if (structuredDataTypes.length === 0 || (!hasFAQ && !hasOrg)) {
try {
const response = await fetch(`https://api.allorigins.win/raw?url=${encodeURIComponent(this.url)}`);
const html = await response.text();

if (!hasFAQ) {
hasFAQ = html.includes(‘FAQPage’) || html.includes(‘”@type”:”Question”‘) || html.includes(‘”@type”: “Question”‘);
}
if (!hasOrg) {
hasOrg = html.includes(‘Organization’) || html.includes(‘LocalBusiness’) || html.includes(‘ProfessionalService’);
}
} catch (error) {
console.log(‘Schema backup check failed:’, error);
}
}

checks.push({
title: ‘FAQ Structured Data’,
passed: hasFAQ,
detail: hasFAQ
? ‘FAQ schema found – helps AI understand Q&A’
: ‘No FAQ schema – add for better AI discovery’,
verified: true
});

checks.push({
title: ‘Business Entity Markup’,
passed: hasOrg,
detail: hasOrg
? ‘Organization/Business schema detected’
: ‘Missing business entity markup – AI needs this to understand your business’,
verified: true
});

const goodSEOScore = this.pageSpeedData.seoScore >= 80;
checks.push({
title: ‘AI-Friendly SEO Score’,
passed: goodSEOScore,
detail: goodSEOScore
? `Strong SEO foundation (${Math.round(this.pageSpeedData.seoScore)}/100) supports AI discovery`
: `SEO score ${Math.round(this.pageSpeedData.seoScore)}/100 – improvements needed for better AI understanding`,
verified: true
});

} else {
checks.push({
title: ‘AI Discoverability Analysis Limited’,
passed: null,
detail: ‘Unable to complete automated AI discoverability analysis. Manual review recommended for FAQ schema, business entity markup, and content depth.’,
verified: false
});
}

this.results.ai = checks;
}

async checkSocial() {
const checks = [];

try {
const response = await fetch(`https://api.allorigins.win/raw?url=${encodeURIComponent(this.url)}`);
const html = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(html, ‘text/html’);

const hasOG = doc.querySelector(‘meta[property^=”og:”]’);
const ogTitle = doc.querySelector(‘meta[property=”og:title”]’);
const ogDesc = doc.querySelector(‘meta[property=”og:description”]’);
const ogImage = doc.querySelector(‘meta[property=”og:image”]’);
const hasCompleteOG = hasOG && ogTitle && ogDesc && ogImage;

checks.push({
title: ‘Social Media Tags (Open Graph)’,
passed: hasCompleteOG,
detail: hasCompleteOG
? ‘Complete Open Graph tags – optimized for social sharing’
: hasOG
? ‘Partial OG tags – missing title, description, or image’
: ‘Missing OG tags – content may not display well when shared’,
verified: true
});

const hasTwitter = doc.querySelector(‘meta[name^=”twitter:”]’);
const twitterCard = doc.querySelector(‘meta[name=”twitter:card”]’);
checks.push({
title: ‘Twitter Card Tags’,
passed: !!twitterCard,
detail: twitterCard
? `Twitter cards configured (${twitterCard.content})`
: hasTwitter
? ‘Partial Twitter tags – missing card type’
: ‘Missing Twitter card tags’,
verified: true
});

const hasTestimonials = html.toLowerCase().includes(‘testimonial’) ||
html.toLowerCase().includes(‘review’) ||
html.includes(‘Review’);
checks.push({
title: ‘Social Proof on Site’,
passed: hasTestimonials,
detail: hasTestimonials
? ‘Testimonials or reviews found on page’
: ‘No visible social proof – consider adding testimonials’,
verified: true
});

} catch (error) {
checks.push({
title: ‘Social Tags Analysis Limited’,
passed: null,
detail: ‘Site security settings prevent automated scanning. Manual review recommended for Open Graph tags, Twitter cards, and social proof elements.’,
verified: false
});
}

this.results.social = checks;
}

calculateScores() {
const sections = [‘seo’, ‘local’, ‘ai’, ‘social’];
const sectionScores = {};

sections.forEach(section => {
const checks = this.results[section];
const verifiedChecks = checks.filter(c => c.verified);
const passedChecks = verifiedChecks.filter(c => c.passed);

sectionScores[section] = verifiedChecks.length > 0
? Math.round((passedChecks.length / verifiedChecks.length) * 100)
: 0;
});

const overallScore = Math.round(
(sectionScores.seo + sectionScores.local + sectionScores.ai + sectionScores.social) / 4
);

return {
overall: overallScore,
sections: sectionScores,
details: this.results
};
}
}

// UI Controller
const UI = {
showLoading() {
document.getElementById(‘input-view’).classList.add(‘hidden’);
document.getElementById(‘loading-view’).classList.remove(‘hidden’);
document.getElementById(‘results-view’).classList.remove(‘active’);
},

showResults(scores) {
document.getElementById(‘loading-view’).classList.add(‘hidden’);
document.getElementById(‘results-view’).classList.add(‘active’);

this.renderOverallScore(scores.overall);
this.renderSectionResults(scores);
},

renderOverallScore(score) {
const scoreElement = document.getElementById(‘overall-score’);
scoreElement.textContent = score;

let scoreClass = ‘score-critical’;
if (score >= 80) scoreClass = ‘score-excellent’;
else if (score >= 60) scoreClass = ‘score-good’;
else if (score >= 40) scoreClass = ‘score-needs-work’;

scoreElement.className = `overall-score ${scoreClass}`;
},

renderSectionResults(scores) {
const container = document.getElementById(‘section-results’);
container.innerHTML = ”;

const sections = [
{ key: ‘seo’, title: ‘SEO Fundamentals’, icon: ‘🔍’ },
{ key: ‘local’, title: ‘Local Search Presence’, icon: ‘📍’ },
{ key: ‘ai’, title: ‘AI Discoverability’, icon: ‘🤖’ },
{ key: ‘social’, title: ‘Social Proof & Authority’, icon: ‘⭐’ }
];

sections.forEach(section => {
const sectionScore = scores.sections[section.key];
const checks = scores.details[section.key];

const sectionHTML = `

${section.icon} ${section.title}

${sectionScore}/100

${checks.map(check => this.renderCheck(check)).join(”)}

`;

container.innerHTML += sectionHTML;
});
},

renderCheck(check) {
if (check.passed === null) {
return `

⚠️
${check.title}
Manual Review
${check.detail}

`;
}

const status = check.passed ? ‘passed’ : ‘failed’;
const icon = check.passed ? ‘✓’ : ‘✗’;
const badge = check.verified
? ‘Verified
: ‘Self-Reported‘;

return `

${icon}
${check.title}
${badge}
${check.detail}

`;
},

getScoreClass(score) {
if (score >= 80) return ‘score-excellent’;
if (score >= 60) return ‘score-good’;
if (score >= 40) return ‘score-needs-work’;
return ‘score-critical’;
}
};

// Event handlers
document.getElementById(‘audit-form’).addEventListener(‘submit’, async (e) => {
e.preventDefault();

const url = document.getElementById(‘website-url’).value;
const businessName = document.getElementById(‘business-name’).value;

UI.showLoading();

try {
const auditor = new VisibilityAuditor(url, businessName);
const scores = await auditor.runAudit();

window.auditResults = { url, businessName, scores };

UI.showResults(scores);
} catch (error) {
console.error(‘Audit error:’, error);
alert(‘There was an error running the audit. Please try again.’);
document.getElementById(‘loading-view’).classList.add(‘hidden’);
document.getElementById(‘input-view’).classList.remove(‘hidden’);
}
});

document.getElementById(’email-form’).addEventListener(‘submit’, async (e) => {
e.preventDefault();

const email = document.getElementById(’email-input’).value;
const submitBtn = e.target.querySelector(‘button’);

submitBtn.disabled = true;
submitBtn.textContent = ‘Sending…’;

try {
const results = window.auditResults;
const reportHTML = generateEmailReport(results);

const formData = new FormData();
formData.append(‘access_key’, CONFIG.web3formsKey);
formData.append(‘subject’, ‘Your AI Visibility Audit Results’);
formData.append(‘from_name’, ‘AI Visibility Audit’);
formData.append(’email’, email);
formData.append(‘message’, reportHTML);

const response = await fetch(‘https://api.web3forms.com/submit’, {
method: ‘POST’,
body: formData
});

const data = await response.json();

if (data.success) {
alert(‘Report sent! Check your email for detailed recommendations.’);
document.getElementById(’email-input’).value = ”;
} else {
throw new Error(‘Failed to send email’);
}

} catch (error) {
console.error(‘Email error:’, error);
alert(‘There was an error sending the report. Please try again.’);
}

submitBtn.disabled = false;
submitBtn.textContent = ‘Send My Report’;
});

function generateEmailReport(results) {
const { url, businessName, scores } = results;

let report = `AI VISIBILITY AUDIT RESULTS\n`;
report += `Business: ${businessName}\n`;
report += `Website: ${url}\n`;
report += `Overall Score: ${scores.overall}/100\n\n`;
report += `=====================================\n\n`;

const sections = [
{ key: ‘seo’, title: ‘SEO FUNDAMENTALS’ },
{ key: ‘local’, title: ‘LOCAL SEARCH PRESENCE’ },
{ key: ‘ai’, title: ‘AI DISCOVERABILITY’ },
{ key: ‘social’, title: ‘SOCIAL PROOF & AUTHORITY’ }
];

sections.forEach(section => {
report += `${section.title}\n`;
report += `Score: ${scores.sections[section.key]}/100\n\n`;

scores.details[section.key].forEach(check => {
let status;
if (check.passed === null) {
status = ‘⚠ MANUAL REVIEW NEEDED’;
} else if (check.passed) {
status = ‘✓ PASS’;
} else {
status = ‘✗ FAIL’;
}
report += `${status}: ${check.title}\n`;
report += ` ${check.detail}\n\n`;
});

report += `————————————-\n\n`;
});

report += `NEXT STEPS:\n\n`;
report += `Most businesses can improve their visibility score by 40-60 points in 90 days with the right strategy.\n\n`;
report += `Want expert help fixing these issues?\n`;
report += `Schedule a consultation: https://aimframeworkbook.com/contact\n\n`;
report += `—\n`;
report += `This report was generated by the AI Visibility Audit tool.\n`;
report += `© Wizard of Ads Chuck McKay\n`;

return report;
}