},
'uses-responsive-images': {
impact: 'High impact - images are usually the biggest files',
steps: ['Resize images to actual display size (not 2000px for a 400px thumbnail)', 'Compress with TinyPNG.com (free) or ShortPixel', 'Use WebP format for 30% smaller files', 'Add width/height attributes to prevent layout shift']
},
'offscreen-images': {
impact: 'Medium impact on initial load',
steps: ['Add loading="lazy" to images below the fold', 'Example: <img src="photo.jpg" loading="lazy">', 'WordPress does this automatically since version 5.5']
},
'unminified-css': {
impact: 'Low-medium impact',
steps: ['Use a CSS minifier tool online', 'WordPress: "Autoptimize" plugin does this automatically', 'Remove all comments and whitespace from production CSS']
},
'unminified-javascript': {
impact: 'Low-medium impact',
steps: ['Use a JS minifier tool online', 'WordPress: "Autoptimize" plugin does this automatically', 'Most modern build tools (Webpack, Vite) do this by default']
},
'uses-text-compression': {
impact: 'High impact - can reduce file sizes by 70%',
steps: ['Enable GZIP on your server', 'For cPanel: Enable in "Optimize Website" section', 'For Apache .htaccess add: AddOutputFilterByType DEFLATE text/html text/css application/javascript', 'Ask your hosting provider - most have this option']
},
'uses-long-cache-ttl': {
impact: 'Medium impact for returning visitors',
steps: ['Set browser caching for static files (images, CSS, JS)', 'WordPress: Use WP Super Cache or W3 Total Cache', 'These tell browsers to remember your files instead of re-downloading']
},
'meta-description': {
impact: 'Critical for SEO click-through rates',
steps: ['Add to your <head>: <meta name="description" content="Your page description here">', 'Keep it 120-160 characters', 'Include your main keyword naturally', 'Make it compelling - this is what shows in Google results!']
},
'document-title': {
impact: 'Critical for SEO',
steps: ['Every page needs a unique <title> tag', 'Keep under 60 characters so Google doesn\'t cut it off', 'Put your main keyword near the beginning', 'Format: "Main Keyword - Secondary | Brand Name"']
},
'viewport': {
impact: 'Critical - site won\'t work on mobile without this',
steps: ['Add to your <head>:', '<meta name="viewport" content="width=device-width, initial-scale=1">', 'This tells phones how to display your page properly']
},
'font-size': {
impact: 'Important for mobile users',
steps: ['Use minimum 16px font size for body text', 'Test your site on a real phone', 'If you have to pinch-zoom to read, fonts are too small']
},
'tap-targets': {
impact: 'Important for mobile usability',
steps: ['Make buttons/links at least 48x48 pixels', 'Add padding around clickable elements', 'Space links at least 8px apart', 'Test on your phone - can you tap accurately?']
},
'image-alt': {
impact: 'Important for accessibility & SEO',
steps: ['Add alt text to ALL images', 'Describe what\'s in the image, don\'t just say "image"', 'Good: alt="Red Toyota Corolla rental car"', 'Bad: alt="car" or alt="image1"']
},
'link-text': {
impact: 'Important for accessibility & SEO',
steps: ['Never use "click here" as link text', 'Use descriptive text: "View our pricing" not "Click here"', 'Screen readers and Google both prefer descriptive links']
},
'largest-contentful-paint': {
impact: 'Core Web Vital - affects Google ranking',
steps: ['Optimize your largest visible image (usually the hero)', 'Preload it: <link rel="preload" as="image" href="hero.jpg">', 'Use next-gen formats (WebP)', 'Target: under 2.5 seconds']
},
'cumulative-layout-shift': {
impact: 'Core Web Vital - stops annoying page jumps',
steps: ['Always set width and height on images: <img width="800" height="600">', 'Reserve space for ads before they load', 'Don\'t insert content above existing content']
},
'first-contentful-paint': {
impact: 'How fast users see something',
steps: ['Reduce server response time (better hosting)', 'Eliminate render-blocking CSS/JS', 'Preload critical resources']
},
'total-blocking-time': {
impact: 'How responsive your page feels',
steps: ['Reduce JavaScript - remove what you don\'t need', 'Break up long tasks', 'Defer non-critical JavaScript']
},
'default': {
impact: 'Varies',
steps: ['Check Google\'s PageSpeed documentation for specific guidance', 'Test changes one at a time', 'Consider consulting a web professional']
}
};
async function runAnalysis() {
const emailInput = document.getElementById('emailInput');
const urlInput = document.getElementById('urlInput');
let email = emailInput.value.trim();
let url = urlInput.value.trim();
if (!email || !email.includes('@')) { alert('Please enter a valid email'); emailInput.focus(); return; }
if (!url) { alert('Please enter a website URL'); urlInput.focus(); return; }
if (!url.startsWith('http://') && !url.startsWith('https://')) url = 'https://' + url;
try { new URL(url); } catch(e) { alert('Please enter a valid URL'); return; }
// Submit to Ezy Form Handler
fetch('https://ezywebpro.com/wp/wp-json/ezy/v1/submit', {
method: 'POST',
body: JSON.stringify({ email: email, website_url: url, _subject: 'SEO Checker - ' + url, form_source: 'tools/seo-checker' }),
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }
});
document.getElementById('inputForm').style.display = 'none';
document.getElementById('loadingState').classList.add('active');
document.getElementById('analyzeBtn').disabled = true;
// Animate steps
const steps = ['step1', 'step2', 'step3', 'step4', 'step5'];
let currentStep = 0;
const stepInterval = setInterval(() => {
if (currentStep < steps.length) {
if (currentStep > 0) {
document.getElementById(steps[currentStep-1]).classList.remove('active');
document.getElementById(steps[currentStep-1]).classList.add('done');
document.getElementById(steps[currentStep-1]).querySelector('.icon').textContent = 'โ ';
}
document.getElementById(steps[currentStep]).classList.add('active');
currentStep++;
}
}, 3000);
try {
const apiUrl = `${API_URL}?url=${encodeURIComponent(url)}&category=performance&category=accessibility&category=seo&category=best-practices&strategy=mobile`;
console.log('Calling PageSpeed API:', apiUrl);
const response = await fetch(apiUrl);
clearInterval(stepInterval);
console.log('Response status:', response.status);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
console.error('API Error:', errorData);
throw new Error(errorData.error?.message || 'API request failed');
}
const data = await response.json();
console.log('PageSpeed data received:', data.lighthouseResult ? 'Success' : 'No lighthouse data');
if (!data.lighthouseResult) {
throw new Error('No analysis data returned');
}
steps.forEach(s => {
document.getElementById(s).classList.remove('active');
document.getElementById(s).classList.add('done');
document.getElementById(s).querySelector('.icon').textContent = 'โ ';
});
setTimeout(() => displayResults(url, data), 500);
} catch (error) {
clearInterval(stepInterval);
console.error('Analysis error:', error);
let errorMsg = 'We couldn\'t analyze this website. ';
if (error.message.includes('COULD_NOT_RESOLVE')) {
errorMsg += 'The domain could not be found. Please check the URL.';
} else if (error.message.includes('DNS_FAILURE')) {
errorMsg += 'DNS lookup failed. Make sure the website exists.';
} else if (error.message.includes('FAILED_DOCUMENT_REQUEST')) {
errorMsg += 'Could not load the page. The site may be down or blocking requests.';
} else {
errorMsg += 'It may be blocking requests, too slow, or not publicly accessible. Try again in a moment.';
}
showError(errorMsg);
}
}
function showError(msg) {
document.getElementById('loadingState').classList.remove('active');
document.getElementById('errorMessage').textContent = msg;
document.getElementById('errorState').classList.add('active');
}
function displayResults(url, data) {
document.getElementById('loadingState').classList.remove('active');
document.getElementById('resultsContainer').classList.add('active');
document.getElementById('analyzedUrl').textContent = new URL(url).hostname;
const cats = data.lighthouseResult.categories;
const scores = {
performance: Math.round((cats.performance?.score || 0) * 100),
seo: Math.round((cats.seo?.score || 0) * 100),
accessibility: Math.round((cats.accessibility?.score || 0) * 100),
bestPractices: Math.round((cats['best-practices']?.score || 0) * 100)
};
const getClass = s => s >= 90 ? 'good' : s >= 50 ? 'warn' : 'poor';
document.getElementById('scoresGrid').innerHTML = `