If they’re broken across weird symbols or missing entirely, that’s a ranking problem.
Advanced Testing Tool
Would you like to see what LLM sees? Try out the following
The following will give you an instant grades any webpage A-F on how easily AI models like ChatGPT and Claude can read and understand its content, plus gives specific fixes to improve AI-friendliness.
Instructions
Open the webpage in browser.
Open the Devtool with website (use CTRL+shift+I).
Go to console section.
Paste the following code at console (if see a warning then first type allow pasting at console)
Warning: Don’t paste code into the DevTools Console that you don’t understand or haven’t reviewed yourself. This could allow attackers to steal your identity or take control of your computer. Please type ‘allow pasting’ below and press Enter to allow pasting.)
LLM Content Analyzer Code
(async () => {
// Extract visible page text
const visibleText = document.body.innerText.replace(/\s+/g, ' ').trim();
// LLM Friendliness Analyzer
function analyzeLLMFriendliness() {
const scores = {};
let totalScore = 0;
// 1. Text Extraction Quality (25 points)
const sourceHTML = document.documentElement.outerHTML;
const textInSource = sourceHTML.includes(visibleText.slice(0, 100));
const hasImages = document.querySelectorAll('img').length;
const imagesWithAlt = document.querySelectorAll('img[alt]').length;
const altCoverage = hasImages > 0 ? (imagesWithAlt / hasImages) * 100 : 100;
scores.textExtraction = textInSource ? 25 : 10;
totalScore += scores.textExtraction;
// 2. Semantic HTML Structure (25 points)
const h1Count = document.querySelectorAll('h1').length;
const hasHeadings = document.querySelectorAll('h1, h2, h3, h4, h5, h6').length > 0;
const hasParagraphs = document.querySelectorAll('p').length > 0;
const hasLists = document.querySelectorAll('ul, ol').length > 0;
let semanticScore = 0;
if (h1Count === 1) semanticScore += 8;
else if (h1Count > 1) semanticScore += 3;
if (hasHeadings) semanticScore += 7;
if (hasParagraphs) semanticScore += 5;
if (hasLists) semanticScore += 5;
scores.semantic = Math.min(semanticScore, 25);
totalScore += scores.semantic;
// 3. Content Visibility (15 points)
const hiddenElements = document.querySelectorAll('[style*="display: none"], [style*="visibility: hidden"], .hidden').length;
const totalElements = document.querySelectorAll('*').length;
const visibilityRatio = 1 - (hiddenElements / totalElements);
scores.visibility = Math.round(visibilityRatio * 15);
totalScore += scores.visibility;
// 4. Token Efficiency (15 points)
const tokenCount = Math.ceil(visibleText.length / 4); // Rough estimate
const efficiency = Math.min(visibleText.length / tokenCount, 5) / 5; // Characters per token
scores.efficiency = Math.round(efficiency * 15);
totalScore += scores.efficiency;
// 5. Context Clarity (10 points)
const title = document.title || '';
const metaDesc = document.querySelector('meta[name="description"]')?.content || '';
const hasTitle = title.length > 0;
const hasMetaDesc = metaDesc.length > 0;
scores.context = (hasTitle ? 5 : 0) + (hasMetaDesc ? 5 : 0);
totalScore += scores.context;
// 6. Accessibility (10 points)
const linksWithText = document.querySelectorAll('a').length;
const descriptiveLinks = Array.from(document.querySelectorAll('a')).filter(
link => link.textContent && !['click here', 'read more', 'link'].includes(link.textContent.toLowerCase().trim())
).length;
const linkScore = linksWithText > 0 ? (descriptiveLinks / linksWithText) * 10 : 10;
scores.accessibility = Math.round(Math.min(linkScore + (altCoverage / 10), 10));
totalScore += scores.accessibility;
// Calculate grade
let grade = 'F';
if (totalScore >= 90) grade = 'A';
else if (totalScore >= 80) grade = 'B';
else if (totalScore >= 70) grade = 'C';
else if (totalScore >= 60) grade = 'D';
return { scores, totalScore, grade, altCoverage, tokenCount };
}
const analysis = analyzeLLMFriendliness();
// Create enhanced popup UI
const popup = document.createElement('div');
popup.className = 'token-analyzer-popup';
// Inject styles
const styles = `
<style>
.token-analyzer-popup {
position: fixed;
top: 20px;
right: 20px;
width: 560px;
max-height: 90vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
box-shadow: 0 20px 40px rgba(0,0,0,0.15);
z-index: 999999;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.token-header {
background: rgba(255,255,255,0.1);
backdrop-filter: blur(10px);
padding: 20px 24px;
border-bottom: 1px solid rgba(255,255,255,0.2);
color: white;
}
.token-title {
font-size: 20px;
font-weight: 600;
margin: 0;
display: flex;
align-items: center;
gap: 10px;
}
.token-icon {
width: 24px;
height: 24px;
background: rgba(255,255,255,0.2);
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
}
.grade-badge {
margin-left: auto;
padding: 8px 16px;
border-radius: 20px;
font-weight: 700;
font-size: 16px;
}
.grade-A { background: #28a745; }
.grade-B { background: #17a2b8; }
.grade-C { background: #ffc107; color: #000; }
.grade-D { background: #fd7e14; }
.grade-F { background: #dc3545; }
.token-content {
background: white;
max-height: calc(90vh - 80px);
overflow-y: auto;
}
.token-section {
padding: 20px 24px;
border-bottom: 1px solid #f0f0f0;
}
.token-section:last-child {
border-bottom: none;
}
.section-title {
font-size: 14px;
font-weight: 600;
color: #666;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 12px;
}
.text-preview {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 16px;
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
font-size: 12px;
line-height: 1.5;
color: #495057;
max-height: 150px;
overflow-y: auto;
white-space: pre-wrap;
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 12px;
margin-bottom: 16px;
}
.stat-card {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-radius: 8px;
padding: 12px;
text-align: center;
}
.stat-value {
font-size: 20px;
font-weight: 700;
color: #495057;
margin-bottom: 4px;
}
.stat-label {
font-size: 11px;
color: #6c757d;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.llm-scores {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-bottom: 16px;
}
.score-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
background: #f8f9fa;
border-radius: 6px;
font-size: 12px;
}
.score-value {
font-weight: 600;
color: #495057;
}
.btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 10px 16px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
margin-right: 8px;
margin-bottom: 8px;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.btn-secondary {
background: #6c757d;
}
.btn-danger {
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
}
.token-output {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 16px;
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
font-size: 11px;
line-height: 1.4;
max-height: 300px;
overflow-y: auto;
white-space: pre-wrap;
color: #495057;
margin-top: 12px;
}
.loading {
display: flex;
align-items: center;
justify-content: center;
padding: 40px;
color: #6c757d;
}
.spinner {
width: 20px;
height: 20px;
border: 2px solid #e9ecef;
border-top: 2px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 8px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.tabs {
display: flex;
border-bottom: 1px solid #e9ecef;
}
.tab {
padding: 12px 20px;
cursor: pointer;
border-bottom: 2px solid transparent;
font-size: 14px;
font-weight: 500;
color: #666;
transition: all 0.2s ease;
}
.tab.active {
color: #667eea;
border-bottom-color: #667eea;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
</style>
`;
popup.innerHTML = styles + `
<div class="token-header">
<h3 class="token-title">
<div class="token-icon">🤖</div>
LLM Content Analyzer
<div class="grade-badge grade-${analysis.grade}">${analysis.grade} (${analysis.totalScore}/100)</div>
</h3>
</div>
<div class="token-content">
<div class="tabs">
<div class="tab active" data-tab="overview">Overview</div>
<div class="tab" data-tab="llm-grade">LLM Grade</div>
<div class="tab" data-tab="tokens">Tokens</div>
</div>
<div class="tab-content active" id="overview">
<div class="token-section">
<div class="section-title">Content Preview</div>
<div class="text-preview">${visibleText.slice(0, 400)}${visibleText.length > 400 ? '\n\n... (showing first 400 characters)' : ''}</div>
</div>
<div class="token-section">
<div class="section-title">Quick Stats</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value">${visibleText.length.toLocaleString()}</div>
<div class="stat-label">Characters</div>
</div>
<div class="stat-card">
<div class="stat-value">${visibleText.split(/\s+/).length.toLocaleString()}</div>
<div class="stat-label">Words</div>
</div>
<div class="stat-card">
<div class="stat-value">${analysis.tokenCount.toLocaleString()}</div>
<div class="stat-label">Est. Tokens</div>
</div>
</div>
</div>
</div>
<div class="tab-content" id="llm-grade">
<div class="token-section">
<div class="section-title">LLM Friendliness Breakdown</div>
<div class="llm-scores">
<div class="score-item">
<span>Text Extraction</span>
<span class="score-value">${analysis.scores.textExtraction}/25</span>
</div>
<div class="score-item">
<span>HTML Structure</span>
<span class="score-value">${analysis.scores.semantic}/25</span>
</div>
<div class="score-item">
<span>Content Visibility</span>
<span class="score-value">${analysis.scores.visibility}/15</span>
</div>
<div class="score-item">
<span>Token Efficiency</span>
<span class="score-value">${analysis.scores.efficiency}/15</span>
</div>
<div class="score-item">
<span>Context Clarity</span>
<span class="score-value">${analysis.scores.context}/10</span>
</div>
<div class="score-item">
<span>Accessibility</span>
<span class="score-value">${analysis.scores.accessibility}/10</span>
</div>
</div>
<div id="recommendations"></div>
</div>
</div>
<div class="tab-content" id="tokens">
<div class="token-section">
<div class="section-title">Token Analysis</div>
<button class="btn" id="showTokensBtn">
🔢 Analyze Tokens
</button>
<button class="btn btn-secondary" id="copyTextBtn">
📋 Copy Text
</button>
<div id="tokenOutput" class="token-output" style="display: none;"></div>
</div>
</div>
<div class="token-section" style="border-bottom: none;">
<button class="btn btn-danger" id="closePopup">
✕ Close
</button>
</div>
</div>
`;
document.body.appendChild(popup);
// Tab functionality
document.querySelectorAll('.tab').forEach(tab => {
tab.onclick = () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
tab.classList.add('active');
document.getElementById(tab.dataset.tab).classList.add('active');
};
});
// Generate recommendations
function generateRecommendations() {
const recs = [];
if (analysis.scores.textExtraction < 20) {
recs.push("⚠️ Text may not be in HTML source - check for image-based or JS-rendered content");
}
if (analysis.scores.semantic < 20) {
recs.push("📝 Use semantic HTML: proper h1-h6 headings, p tags, lists");
}
if (analysis.scores.visibility < 10) {
recs.push("👁️ Too much hidden content - ensure main text is visible");
}
if (analysis.scores.efficiency < 10) {
recs.push("⚡ Improve token efficiency - remove repetitive text and fluff");
}
if (analysis.scores.context < 5) {
recs.push("🏷️ Add meta title and description for better context");
}
if (analysis.scores.accessibility < 7) {
recs.push("♿ Improve accessibility: alt text for images, descriptive links");
}
if (recs.length === 0) {
recs.push("✅ Great job! This page is very LLM-friendly");
}
document.getElementById('recommendations').innerHTML =
'<div class="section-title" style="margin-top: 16px;">Recommendations</div>' +
recs.map(rec => `<div style="margin-bottom: 8px; font-size: 13px;">${rec}</div>`).join('');
}
generateRecommendations();
// Event handlers
document.getElementById('closePopup').onclick = () => {
popup.style.animation = 'slideIn 0.3s ease-out reverse';
setTimeout(() => popup.remove(), 300);
};
document.getElementById('copyTextBtn').onclick = () => {
navigator.clipboard.writeText(visibleText).then(() => {
const btn = document.getElementById('copyTextBtn');
const originalText = btn.innerHTML;
btn.innerHTML = '✓ Copied!';
btn.style.background = '#28a745';
setTimeout(() => {
btn.innerHTML = originalText;
btn.style.background = '';
}, 2000);
});
};
document.getElementById('showTokensBtn').onclick = async () => {
const btn = document.getElementById('showTokensBtn');
const output = document.getElementById('tokenOutput');
btn.disabled = true;
btn.innerHTML = '<div class="spinner"></div>Loading...';
output.style.display = 'block';
output.innerHTML = '<div class="loading"><div class="spinner"></div>Loading tokenizer...</div>';
try {
await new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/gpt-tokenizer/dist/cl100k_base.js';
script.onload = resolve;
script.onerror = () => reject(new Error('Failed to load tokenizer.'));
document.head.appendChild(script);
});
const { encode } = window.GPTTokenizer_cl100k_base;
const tokens = encode(visibleText);
const words = visibleText.split(/\s+/);
const maxTokensToShow = 100;
const tokenPairs = tokens.slice(0, maxTokensToShow).map((id, idx) => {
const word = words[idx] || '...';
return `${String(idx + 1).padStart(3, ' ')}: "${word}" → ${id}`;
});
const analysisResult = `Token Analysis Results
${'='.repeat(50)}
📊 Statistics:
• Total Tokens: ${tokens.length.toLocaleString()}
• Characters: ${visibleText.length.toLocaleString()}
• Words: ${words.length.toLocaleString()}
• Chars/Token: ${(visibleText.length / tokens.length).toFixed(2)}
• Compression: ${((1 - tokens.length / visibleText.length) * 100).toFixed(1)}%
🔢 Token Mappings (first ${Math.min(maxTokensToShow, tokens.length)}):
${tokenPairs.join('\n')}${tokens.length > maxTokensToShow ? `\n\n... and ${(tokens.length - maxTokensToShow).toLocaleString()} more tokens` : ''}
💡 LLM Context:
• GPT-4 Context: ~128k tokens (${Math.round(tokens.length / 128000 * 100)}% used)
• Claude Context: ~200k tokens (${Math.round(tokens.length / 200000 * 100)}% used)`;
output.textContent = analysisResult;
btn.innerHTML = '✓ Complete';
btn.style.background = '#28a745';
} catch (err) {
output.textContent = '❌ Error loading tokenizer. Check internet connection.';
btn.innerHTML = '❌ Error';
btn.style.background = '#dc3545';
}
};
})();
What the Tool Shows:
A-F Grade with 100-point scoring system
Content preview showing what AI sees
6 scoring categories: Text extraction, HTML structure, visibility, token efficiency, context clarity, accessibility
Specific recommendations for improvement
Token analysis with compression ratios and context usage
It instantly identifies LLM optimization opportunities and provides actionable fixes for better AI discoverability.