@casoon/auditmysite
Version:
Professional website analysis suite with robust accessibility testing, Core Web Vitals performance monitoring, SEO analysis, and content optimization insights. Features isolated browser contexts, retry mechanisms, and comprehensive API endpoints for profe
833 lines (725 loc) • 28.7 kB
JavaScript
/**
* Comprehensive HTML Template for AuditMySite Reports (v1.8.4)
* Features: Sticky navigation, KPIs, SEO/Performance sections, Interactive filters
*/
function getComprehensiveHtmlTemplate(data) {
const {
domain,
testedPages = 0,
totalPages = 0,
totalErrors = 0,
successRate = 0,
totalDuration = '0s',
groupedAccessibilityIssues = [],
performanceResults = [],
seoResults = []
} = data;
// Generate accessibility table
const generateAccessibilityTable = () => {
if (!groupedAccessibilityIssues || groupedAccessibilityIssues.length === 0) {
return '<div class="no-issues"><h3>🎉 No accessibility issues found</h3><p>All tested pages passed accessibility checks.</p></div>';
}
let tableHTML = `
<div class="table-container">
<div class="table-header">
<h3>Accessibility Issues</h3>
<button class="copy-btn" onclick="copyToClipboard('accessibility-table')">📋 Copy Data</button>
</div>
<div class="table-wrapper">
<table id="accessibility-table" class="data-table">
<thead>
<tr>
<th>Type</th>
<th>Page</th>
<th>Element</th>
<th>Message</th>
</tr>
</thead>
<tbody>`;
groupedAccessibilityIssues.forEach(group => {
group.issues.forEach(issue => {
const severity = issue.type || 'error';
tableHTML += `
<tr class="${severity}">
<td><span class="${severity}">${severity.toUpperCase()}</span></td>
<td>${issue.pageUrl || 'Unknown'}</td>
<td><code>${issue.selector || 'N/A'}</code></td>
<td>${issue.message || 'No message'}</td>
</tr>`;
});
});
tableHTML += '</tbody></table></div></div>';
return tableHTML;
};
// Generate performance table
const generatePerformanceTable = () => {
if (!performanceResults || performanceResults.length === 0) {
return '<div class="no-data"><h3>⏱️ No performance data available</h3><p>Performance metrics were not collected for this audit.</p></div>';
}
let tableHTML = `
<div class="table-container">
<div class="table-header">
<h3>Performance Metrics</h3>
<button class="copy-btn" onclick="copyToClipboard('performance-table')">📋 Copy Data</button>
</div>
<div class="table-wrapper">
<table id="performance-table" class="data-table">
<thead>
<tr>
<th>Page</th>
<th>FCP</th>
<th>LCP</th>
<th>CLS</th>
<th>Speed Index</th>
<th>Grade</th>
</tr>
</thead>
<tbody>`;
performanceResults.forEach(result => {
const grade = result.grade || 'N/A';
tableHTML += `
<tr>
<td><strong>${result.url || 'Unknown'}</strong></td>
<td>${result.fcp ? (result.fcp / 1000).toFixed(2) + 's' : 'N/A'}</td>
<td>${result.lcp ? (result.lcp / 1000).toFixed(2) + 's' : 'N/A'}</td>
<td>${result.cls ? result.cls.toFixed(3) : 'N/A'}</td>
<td>${result.speedIndex ? Math.round(result.speedIndex) + 'ms' : 'N/A'}</td>
<td><span class="grade grade-${grade}">${grade}</span></td>
</tr>`;
});
tableHTML += '</tbody></table></div></div>';
return tableHTML;
};
// Generate SEO table
const generateSeoTable = () => {
if (!seoResults || seoResults.length === 0) {
return '<div class="no-data"><h3>🔍 No SEO data available</h3><p>SEO analysis was not performed for this audit.</p></div>';
}
let tableHTML = `
<div class="table-container">
<div class="table-header">
<h3>SEO Analysis</h3>
<button class="copy-btn" onclick="copyToClipboard('seo-table')">📋 Copy Data</button>
</div>
<div class="table-wrapper">
<table id="seo-table" class="data-table">
<thead>
<tr>
<th>Page & Title</th>
<th>SEO Score</th>
<th>Meta Description</th>
<th>Headings</th>
<th>Grade</th>
</tr>
</thead>
<tbody>`;
seoResults.forEach(result => {
const grade = result.grade || result.seoGrade || 'N/A';
const score = result.overallSEOScore || result.seoScore || 'N/A';
const headingStructure = result.headings ?
`H1: ${result.headings.h1 || 0}, H2: ${result.headings.h2 || 0}` : 'N/A';
tableHTML += `
<tr>
<td>
<div class="page-info">
<strong>${result.url || 'Unknown'}</strong>
<div class="page-title">${result.title || 'No title'}</div>
</div>
</td>
<td><strong>${score}${typeof score === 'number' ? '/100' : ''}</strong></td>
<td>${result.metaDescription ?
(result.metaDescription.length > 50 ?
result.metaDescription.substring(0, 50) + '...' :
result.metaDescription) : 'Missing'}</td>
<td>${headingStructure}</td>
<td><span class="grade grade-${grade}">${grade}</span></td>
</tr>`;
});
tableHTML += '</tbody></table></div>';
// Add Advanced SEO Features Section
tableHTML += '<div class="advanced-seo-section" style="margin-top: 2rem;">';
tableHTML += '<h3 style="margin-bottom: 1rem;">🚀 Advanced SEO Analysis</h3>';
seoResults.forEach(result => {
if (result.semanticSEO || result.voiceSearchOptimization || result.eatAnalysis) {
tableHTML += `<div class="advanced-seo-card" style="background: var(--surface-color); padding: 1.5rem; margin-bottom: 1rem; border-radius: 0.5rem; border: 1px solid var(--border-color);">`;
tableHTML += `<h4 style="color: var(--text-primary); margin-bottom: 1rem;">🌐 ${result.url || 'Unknown'}</h4>`;
// Semantic SEO
if (result.semanticSEO) {
tableHTML += `<div class="seo-metric" style="margin-bottom: 1rem;">`;
tableHTML += `<h5 style="color: var(--text-secondary); margin-bottom: 0.5rem;">🧠 Semantic SEO</h5>`;
tableHTML += `<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 0.5rem; font-size: 0.875rem;">`;
tableHTML += `<div><strong>Semantic Score:</strong> ${result.semanticSEO.semanticScore}/100</div>`;
tableHTML += `<div><strong>Content Depth:</strong> ${result.semanticSEO.contentDepthScore}/100</div>`;
tableHTML += `<div><strong>Topic Clusters:</strong> ${result.semanticSEO.topicClusters.slice(0, 3).join(', ')}</div>`;
tableHTML += `<div><strong>LSI Keywords:</strong> ${result.semanticSEO.lsiKeywords.slice(0, 3).join(', ') || 'None'}</div>`;
tableHTML += `</div></div>`;
}
// Voice Search Optimization
if (result.voiceSearchOptimization) {
tableHTML += `<div class="seo-metric" style="margin-bottom: 1rem;">`;
tableHTML += `<h5 style="color: var(--text-secondary); margin-bottom: 0.5rem;">🎤 Voice Search Optimization</h5>`;
tableHTML += `<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 0.5rem; font-size: 0.875rem;">`;
tableHTML += `<div><strong>Voice Score:</strong> ${result.voiceSearchOptimization.voiceSearchScore}/100</div>`;
tableHTML += `<div><strong>Question Phrases:</strong> ${result.voiceSearchOptimization.questionPhrases}</div>`;
tableHTML += `<div><strong>Conversational:</strong> ${result.voiceSearchOptimization.conversationalContent ? '✅ Yes' : '❌ No'}</div>`;
tableHTML += `</div></div>`;
}
// E-A-T Analysis
if (result.eatAnalysis) {
tableHTML += `<div class="seo-metric" style="margin-bottom: 1rem;">`;
tableHTML += `<h5 style="color: var(--text-secondary); margin-bottom: 0.5rem;">🏆 E-A-T Analysis</h5>`;
tableHTML += `<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 0.5rem; font-size: 0.875rem;">`;
tableHTML += `<div><strong>E-A-T Score:</strong> ${result.eatAnalysis.eatScore}/100</div>`;
tableHTML += `<div><strong>Author Present:</strong> ${result.eatAnalysis.authorPresence ? '✅ Yes' : '❌ No'}</div>`;
tableHTML += `<div><strong>Trust Signals:</strong> ${result.eatAnalysis.trustSignals.length}</div>`;
tableHTML += `<div><strong>Expertise Indicators:</strong> ${result.eatAnalysis.expertiseIndicators.length}</div>`;
tableHTML += `</div></div>`;
}
tableHTML += '</div>';
}
});
tableHTML += '</div></div>';
return tableHTML;
};
return `
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Accessibility Test Report - ${domain}</title>
<meta name="description" content="Comprehensive accessibility test report generated by auditmysite">
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><circle cx='50' cy='50' r='40' fill='%232563eb'/><text x='50' y='65' text-anchor='middle' fill='white' font-size='40' font-weight='bold'>A</text></svg>">
<!-- CSS -->
<style>
/* CSS Variables für Theming */
:root {
--primary-color: #2563eb;
--secondary-color: #64748b;
--success-color: #10b981;
--warning-color: #f59e0b;
--error-color: #ef4444;
--background-color: #ffffff;
--surface-color: #f8fafc;
--text-primary: #1e293b;
--text-secondary: #64748b;
--border-color: #e2e8f0;
--shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
}
/* Dark Mode */
@media (prefers-color-scheme: dark) {
:root {
--background-color: #0f172a;
--surface-color: #1e293b;
--text-primary: #f1f5f9;
--text-secondary: #94a3b8;
--border-color: #334155;
}
}
/* Reset und Base Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
line-height: 1.6;
color: var(--text-primary);
background-color: var(--background-color);
transition: background-color 0.3s ease;
}
/* Header */
.report-header {
background: linear-gradient(135deg, var(--primary-color), #1d4ed8);
color: white;
padding: 1rem 0;
box-shadow: var(--shadow-lg);
position: sticky;
top: 0;
z-index: 100;
}
.report-nav {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-size: 1.5rem;
font-weight: bold;
display: flex;
align-items: center;
gap: 0.5rem;
}
.logo::before {
content: "🎯";
font-size: 1.8rem;
}
.filter-badges {
display: flex;
list-style: none;
gap: 1rem;
flex-wrap: wrap;
}
.filter-badge {
background: rgba(255, 255, 255, 0.15);
color: white;
border: 2px solid rgba(255, 255, 255, 0.3);
padding: 0.5rem 1rem;
border-radius: 2rem;
cursor: pointer;
transition: all 0.2s ease;
font-size: 0.875rem;
font-weight: 500;
user-select: none;
}
.filter-badge:hover {
background: rgba(255, 255, 255, 0.25);
border-color: rgba(255, 255, 255, 0.5);
}
.filter-badge.active {
background: rgba(255, 255, 255, 0.9);
color: var(--primary-color);
border-color: white;
}
.filter-badge.inactive {
opacity: 0.6;
background: rgba(255, 255, 255, 0.05);
}
/* Main Content */
.main-content {
max-width: 1200px;
margin: 0 auto;
padding: 2rem 1rem;
}
/* Dashboard */
.dashboard {
margin-bottom: 3rem;
}
.kpi-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.kpi-card {
background: var(--surface-color);
padding: 1.5rem;
border-radius: 0.75rem;
box-shadow: var(--shadow);
border: 1px solid var(--border-color);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.kpi-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
.kpi-card h3 {
color: var(--text-secondary);
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 0.5rem;
}
.kpi-value {
font-size: 2rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.kpi-trend {
font-size: 0.875rem;
font-weight: 500;
}
.kpi-trend.positive {
color: var(--success-color);
}
.kpi-trend.negative {
color: var(--error-color);
}
/* Table Styles */
.table-container {
background: var(--surface-color);
border-radius: 0.75rem;
box-shadow: var(--shadow);
border: 1px solid var(--border-color);
margin-bottom: 2rem;
overflow: hidden;
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem;
border-bottom: 1px solid var(--border-color);
background: var(--background-color);
}
.table-header h3 {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary);
}
.copy-btn {
background: var(--primary-color);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
cursor: pointer;
font-size: 0.875rem;
font-weight: 500;
transition: background-color 0.2s ease;
display: flex;
align-items: center;
gap: 0.5rem;
}
.copy-btn:hover {
background: #1d4ed8;
}
.copy-btn:active {
transform: translateY(1px);
}
.table-wrapper {
overflow-x: auto;
}
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
}
.data-table th {
background: var(--background-color);
padding: 1rem;
text-align: left;
font-weight: 600;
color: var(--text-primary);
border-bottom: 2px solid var(--border-color);
position: sticky;
top: 0;
z-index: 10;
}
.data-table td {
padding: 1rem;
border-bottom: 1px solid var(--border-color);
color: var(--text-primary);
}
.data-table tr:hover {
background: var(--background-color);
}
.data-table tr.error {
background: rgba(239, 68, 68, 0.05);
}
.data-table tr.warning {
background: rgba(245, 158, 11, 0.05);
}
.data-table tr.error:hover {
background: rgba(239, 68, 68, 0.1);
}
.data-table tr.warning:hover {
background: rgba(245, 158, 11, 0.1);
}
/* Section styling */
.issues-section {
background: var(--surface-color);
border-radius: 0.75rem;
margin-bottom: 2rem;
overflow: hidden;
border: 1px solid var(--border-color);
transition: all 0.3s ease;
}
.issues-section.hidden {
display: none;
}
.section-title {
font-size: 1.5rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.section-description {
color: var(--text-secondary);
font-size: 0.95rem;
margin-bottom: 1.5rem;
font-style: italic;
}
/* Page info styling for SEO section */
.page-info {
line-height: 1.4;
}
.page-info strong {
color: var(--text-primary);
font-size: 0.95rem;
}
.page-title {
color: var(--text-secondary);
font-size: 0.85rem;
margin-top: 0.25rem;
}
.page-url {
color: var(--text-secondary);
font-size: 0.75rem;
opacity: 0.7;
margin-top: 0.125rem;
}
/* No Data States */
.no-data, .no-issues {
text-align: center;
padding: 3rem 1rem;
color: var(--text-secondary);
}
.no-data h3, .no-issues h3 {
font-size: 1.5rem;
margin-bottom: 1rem;
color: var(--text-primary);
}
/* Toast Notification */
.toast {
position: fixed;
top: 2rem;
right: 2rem;
background: var(--success-color);
color: white;
padding: 1rem 1.5rem;
border-radius: 0.5rem;
box-shadow: var(--shadow-lg);
z-index: 1000;
transform: translateX(100%);
transition: transform 0.3s ease;
}
.toast.show {
transform: translateX(0);
}
/* Grade Badges */
.grade {
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-weight: 600;
font-size: 0.75rem;
}
.grade-A { background: #dcfce7; color: #166534; }
.grade-B { background: #fef3c7; color: #92400e; }
.grade-C { background: #fed7d7; color: #991b1b; }
.grade-D, .grade-F { background: #fee2e2; color: #991b1b; }
/* Severity colors */
.error { color: var(--error-color); font-weight: 600; }
.warning { color: var(--warning-color); font-weight: 600; }
.notice { color: var(--secondary-color); font-weight: 600; }
/* Responsive Design */
@media (max-width: 768px) {
.filter-badges {
justify-content: center;
}
.table-header {
flex-direction: column;
gap: 1rem;
align-items: flex-start;
}
.data-table {
font-size: 0.75rem;
}
.data-table th,
.data-table td {
padding: 0.75rem 0.5rem;
}
}
</style>
</head>
<body>
<header class="report-header">
<nav class="report-nav">
<div class="logo">auditmysite</div>
<div class="filter-badges">
<span class="filter-badge active" data-section="summary">📊 Summary</span>
<span class="filter-badge active" data-section="accessibility">♿ Accessibility</span>
<span class="filter-badge" data-section="performance">⚡ Performance</span>
<span class="filter-badge" data-section="seo">🔍 SEO</span>
</div>
</nav>
</header>
<main class="main-content">
<!-- Dashboard Section -->
<section id="summary" class="dashboard">
<h2 class="section-title">Test Summary</h2>
<div class="kpi-grid">
<div class="kpi-card">
<h3>Success Rate</h3>
<div class="kpi-value">${successRate}%</div>
<div class="kpi-trend positive">Passed</div>
</div>
<div class="kpi-card">
<h3>Pages Tested</h3>
<div class="kpi-value">${testedPages}/${totalPages}</div>
<div class="kpi-trend">Total Pages</div>
</div>
<div class="kpi-card">
<h3>Total Errors</h3>
<div class="kpi-value">${totalErrors}</div>
<div class="kpi-trend negative">Issues Found</div>
</div>
<div class="kpi-card">
<h3>Test Duration</h3>
<div class="kpi-value">${totalDuration}</div>
<div class="kpi-trend">Time Taken</div>
</div>
</div>
</section>
<!-- Accessibility Section -->
<section id="accessibility" class="issues-section">
<div class="table-header">
<h2 class="section-title">♿ Accessibility Issues</h2>
</div>
<div style="padding: 1.5rem;">
<p class="section-description">Web accessibility compliance and WCAG violations analysis</p>
${generateAccessibilityTable()}
</div>
</section>
<!-- Performance Section -->
<section id="performance" class="issues-section">
<div class="table-header">
<h2 class="section-title">⚡ Performance Metrics</h2>
</div>
<div style="padding: 1.5rem;">
<p class="section-description">Web page performance metrics and loading times</p>
${generatePerformanceTable()}
</div>
</section>
<!-- SEO Section -->
<section id="seo" class="issues-section">
<div class="table-header">
<h2 class="section-title">🔍 SEO Analysis</h2>
</div>
<div style="padding: 1.5rem;">
<p class="section-description">Search engine optimization analysis and content structure</p>
${generateSeoTable()}
</div>
</section>
</main>
<!-- Toast Notification -->
<div id="toast" class="toast">
<span id="toast-message">Copied to clipboard!</span>
</div>
<!-- JavaScript für Interaktivität -->
<script>
// Copy to Clipboard Funktion
function copyToClipboard(tableId) {
const table = document.getElementById(tableId);
if (!table) return;
// Erstelle eine temporäre Textarea für das Kopieren
const textarea = document.createElement('textarea');
textarea.value = table.innerText;
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
showToast('Daten in Zwischenablage kopiert!');
} catch (err) {
showToast('Fehler beim Kopieren!');
}
document.body.removeChild(textarea);
}
// Toast Notification
function showToast(message) {
const toast = document.getElementById('toast');
const toastMessage = document.getElementById('toast-message');
toastMessage.textContent = message;
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
// Smooth Scrolling für Navigation
function initSmoothScrolling() {
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
}
// Dark Mode Toggle
function initDarkMode() {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
function updateTheme() {
document.documentElement.classList.toggle('dark', prefersDark.matches);
}
prefersDark.addEventListener('change', updateTheme);
updateTheme();
}
// Filter Badge System
function initFilterSystem() {
const badges = document.querySelectorAll('.filter-badge');
const sections = document.querySelectorAll('.issues-section, .dashboard');
badges.forEach(badge => {
badge.addEventListener('click', function() {
const targetSection = this.getAttribute('data-section');
// Toggle badge state
this.classList.toggle('active');
// Show/hide corresponding section
const section = document.getElementById(targetSection);
if (section) {
if (this.classList.contains('active')) {
section.classList.remove('hidden');
section.style.display = 'block';
} else {
section.classList.add('hidden');
section.style.display = 'none';
}
}
// Update badge appearance based on active state
updateBadgeStates();
});
});
// Initialize: Show summary and accessibility by default, hide others
sections.forEach(function(section) {
const sectionId = section.id;
const badge = document.querySelector('[data-section="' + sectionId + '"]');
if (sectionId === 'summary' || sectionId === 'accessibility') {
section.classList.remove('hidden');
section.style.display = 'block';
if (badge) badge.classList.add('active');
} else {
section.classList.add('hidden');
section.style.display = 'none';
if (badge) badge.classList.remove('active');
}
});
}
function updateBadgeStates() {
const badges = document.querySelectorAll('.filter-badge');
const activeBadges = document.querySelectorAll('.filter-badge.active');
badges.forEach(badge => {
if (activeBadges.length === 0) {
badge.classList.remove('inactive');
} else {
if (badge.classList.contains('active')) {
badge.classList.remove('inactive');
} else {
badge.classList.add('inactive');
}
}
});
}
// Initialize everything when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
initSmoothScrolling();
initDarkMode();
initFilterSystem();
});
</script>
</body>
</html>`;
}
module.exports = { getComprehensiveHtmlTemplate };