Created
June 12, 2025 12:18
-
-
Save dexit/4f198259d1dd100ed63e1db672529bcd to your computer and use it in GitHub Desktop.
heatmap page
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>WCAIG Heatmap Analyzer Tool</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
/* Custom styles that can't be easily achieved with Tailwind */ | |
.heatmap-container { | |
background: linear-gradient(135deg, #1a202c 0%, #2d3748 100%); | |
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3); | |
} | |
.heatmap-cell { | |
transition: all 0.3s ease; | |
border-radius: 4px; | |
} | |
.heatmap-cell:hover { | |
transform: scale(1.05); | |
box-shadow: 0 0 15px rgba(255, 255, 255, 0.1); | |
} | |
.gradient-legend { | |
background: linear-gradient(to right, #4fd1c5, #f6ad55, #f56565); | |
} | |
.custom-scrollbar::-webkit-scrollbar { | |
width: 6px; | |
height: 6px; | |
} | |
.custom-scrollbar::-webkit-scrollbar-track { | |
background: #2d3748; | |
} | |
.custom-scrollbar::-webkit-scrollbar-thumb { | |
background: #4a5568; | |
border-radius: 3px; | |
} | |
@keyframes fade-in { | |
from { opacity: 0; transform: translateY(10px); } | |
to { opacity: 1; transform: translateY(0); } | |
} | |
.animate-fade-in { | |
animation: fade-in 0.3s ease-out forwards; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-900 text-gray-100 min-h-screen"> | |
<!-- Header/Navigation --> | |
<header class="bg-gray-800 shadow-lg"> | |
<div class="container mx-auto px-4 py-3 flex justify-between items-center"> | |
<div class="flex items-center space-x-2"> | |
<i class="fas fa-fire text-2xl text-orange-400"></i> | |
<h1 class="text-xl font-bold">WCAIG <span class="text-blue-400">Heatmap</span> Analyzer</h1> | |
</div> | |
<nav class="hidden md:flex space-x-6"> | |
<a href="#" class="hover:text-blue-400 transition">Home</a> | |
<a href="#" class="hover:text-blue-400 transition">Features</a> | |
<a href="#" class="hover:text-blue-400 transition">Documentation</a> | |
<a href="#" class="hover:text-blue-400 transition">Contact</a> | |
</nav> | |
<button class="md:hidden text-xl" id="mobileMenuBtn"> | |
<i class="fas fa-bars"></i> | |
</button> | |
</div> | |
<!-- Mobile menu --> | |
<div class="md:hidden hidden bg-gray-700 px-4 py-2" id="mobileMenu"> | |
<div class="flex flex-col space-y-2"> | |
<a href="#" class="block py-2 hover:text-blue-400 transition">Home</a> | |
<a href="#" class="block py-2 hover:text-blue-400 transition">Features</a> | |
<a href="#" class="block py-2 hover:text-blue-400 transition">Documentation</a> | |
<a href="#" class="block py-2 hover:text-blue-400 transition">Contact</a> | |
</div> | |
</div> | |
</header> | |
<!-- Main Content --> | |
<main class="container mx-auto px-4 py-8"> | |
<!-- Tool header --> | |
<section class="mb-10"> | |
<h2 class="text-3xl font-bold mb-2">Website Heatmap Analysis</h2> | |
<p class="text-gray-400 max-w-3xl"> | |
Visualize user engagement with our powerful heatmap tool. Identify hotspots, scroll depth, and click patterns to optimize your website's performance. | |
</p> | |
</section> | |
<!-- Upload/Connect section --> | |
<section class="mb-10 bg-gray-800 rounded-lg p-6 shadow-lg"> | |
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6"> | |
<div> | |
<h3 class="text-xl font-semibold mb-2">Analyze Your Website</h3> | |
<p class="text-gray-400 mb-4 md:mb-0">Upload a screenshot or connect to live analytics</p> | |
</div> | |
<div class="flex flex-col sm:flex-row gap-4"> | |
<label for="screenshotUpload" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg flex items-center justify-center gap-2 transition cursor-pointer"> | |
<i class="fas fa-upload"></i> Upload Screenshot | |
<input type="file" id="screenshotUpload" accept="image/*" class="hidden" /> | |
</label> | |
<button class="bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded-lg flex items-center justify-center gap-2 transition"> | |
<i class="fas fa-link"></i> Live Connection | |
</button> | |
</div> | |
</div> | |
</section> | |
<!-- Accessibility Compliance Ribbon --> | |
<section class="bg-gray-800 rounded-lg p-4 mb-6 flex flex-wrap gap-4 items-center"> | |
<div class="flex items-center gap-2"> | |
<span class="font-semibold">WCAG Compliance:</span> | |
<span class="bg-green-600 text-white text-xs px-2 py-1 rounded">A</span> | |
<span class="bg-blue-600 text-white text-xs px-2 py-1 rounded">AA</span> | |
<span class="bg-purple-600 text-white text-xs px-2 py-1 rounded">AAA</span> | |
</div> | |
<div class="flex items-center gap-2"> | |
<span class="font-semibold">Contrast Ratio:</span> | |
<span class="bg-gray-700 text-white text-xs px-2 py-1 rounded">4.5:1 (Min AA)</span> | |
<span class="bg-gray-700 text-white text-xs px-2 py-1 rounded">7:1 (Min AAA)</span> | |
</div> | |
<div class="flex items-center gap-2"> | |
<span class="font-semibold">Text Size:</span> | |
<span class="bg-gray-700 text-white text-xs px-2 py-1 rounded">≥14pt (AA)</span> | |
<span class="bg-gray-700 text-white text-xs px-2 py-1 rounded">≥18pt (AAA)</span> | |
</div> | |
</section> | |
<!-- Heatmap visualization --> | |
<section class="heatmap-container rounded-xl overflow-hidden mb-10 p-6"> | |
<div class="flex justify-between items-center mb-4"> | |
<h3 class="text-xl font-semibold">Heatmap Visualization</h3> | |
<div class="flex items-center space-x-4"> | |
<div class="flex items-center space-x-2"> | |
<span class="text-sm text-gray-300">Type:</span> | |
<select class="bg-gray-700 text-white rounded px-3 py-1 border border-gray-600"> | |
<option>Click Density</option> | |
<option>Scroll Depth</option> | |
<option>Attention</option> | |
<option>Movement</option> | |
</select> | |
</div> | |
<div class="flex items-center space-x-2"> | |
<span class="text-sm text-gray-300">Intensity:</span> | |
<input type="range" min="0" max="100" value="75" class="w-24"> | |
</div> | |
</div> | |
</div> | |
<!-- Heatmap grid --> | |
<div class="relative h-80 md:h-96 xl:h-[500px] w-full"> | |
<div class="absolute inset-0 bg-gray-700 rounded-lg flex items-center justify-center"> | |
<canvas id="heatmapCanvas" class="absolute top-0 left-0 w-full h-full"></canvas> | |
<div id="imageOverlay" class="absolute inset-0 opacity-20 pointer-events-none hidden"> | |
<div class="grid grid-cols-12 grid-rows-6 gap-2 w-full h-full p-4"> | |
<!-- Generate heatmap cells with random intensities for demo --> | |
<script> | |
// Generate random heatmap cells | |
document.addEventListener('DOMContentLoaded', function() { | |
const container = document.querySelector('.grid'); | |
if (container) { | |
let html = ''; | |
for (let row = 0; row < 6; row++) { | |
for (let col = 0; col < 12; col++) { | |
// Generate random intensity (0-100) | |
const intensity = Math.floor(Math.random() * 30) + 70 - Math.abs(6 - (row + col)); | |
// Map intensity to color | |
let bgClass; | |
if (intensity > 80) bgClass = 'bg-red-500'; | |
else if (intensity > 60) bgClass = 'bg-orange-400'; | |
else if (intensity > 40) bgClass = 'bg-yellow-400'; | |
else if (intensity > 20) bgClass = 'bg-green-400'; | |
else bgClass = 'bg-blue-400'; | |
// Add opacity based on intensity | |
const opacity = Math.max(0.2, intensity / 100); | |
html += `<div class="heatmap-cell ${bgClass} opacity-${Math.floor(opacity * 10)*10}" | |
style="opacity: ${opacity};" | |
data-intensity="${intensity}"></div>`; | |
} | |
} | |
container.innerHTML = html; | |
} | |
}); | |
</script> | |
</div> | |
<!-- Overlay website screenshot placeholder --> | |
<div class="absolute inset-0 opacity-20 pointer-events-none"> | |
<div class="w-full h-full flex items-center justify-center"> | |
<div class="text-center"> | |
<i class="fas fa-image text-7xl text-gray-500 mb-4"></i> | |
<p class="text-gray-500">Website screenshot overlay</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Contrast Grid Placeholder (will be populated by JS after image upload) --> | |
<div id="contrastAnalysis" class="hidden bg-gray-800 rounded-lg p-6 mt-6"> | |
<h4 class="text-lg font-semibold mb-4">Contrast Grid Analysis (32px × 32px blocks)</h4> | |
<div class="grid grid-cols-6 gap-2 mb-4" id="contrastGrid"> | |
<!-- JS will populate this with contrast blocks --> | |
</div> | |
<div class="bg-gray-700 rounded-lg p-4"> | |
<div class="flex flex-wrap gap-4 mb-4"> | |
<div class="flex items-center gap-2"> | |
<div class="w-4 h-4 bg-green-500 rounded-sm"></div> | |
<span>AAA (7:1+)</span> | |
</div> | |
<div class="flex items-center gap-2"> | |
<div class="w-4 h-4 bg-blue-500 rounded-sm"></div> | |
<span>AA (4.5:1+)</span> | |
</div> | |
<div class="flex items-center gap-2"> | |
<div class="w-4 h-4 bg-yellow-500 rounded-sm"></div> | |
<span>Minimum (3:1)</span> | |
</div> | |
<div class="flex items-center gap-2"> | |
<div class="w-4 h-4 bg-red-500 rounded-sm"></div> | |
<span>Fail (<3:1)</span> | |
</div> | |
</div> | |
<p class="text-sm text-gray-400">Each block represents 32×32 pixel area. Hover for details.</p> | |
</div> | |
</div> | |
<!-- Legend --> | |
<div class="mt-4 flex items-center justify-between"> | |
<div class="flex items-center space-x-2"> | |
<span class="text-sm text-gray-300">Legend:</span> | |
<div class="flex items-center"> | |
<div class="w-24 h-4 rounded gradient-legend"></div> | |
<div class="flex justify-between w-24 text-xs px-1"> | |
<span>Low</span> | |
<span>Medium</span> | |
<span>High</span> | |
</div> | |
</div> | |
</div> | |
<button class="text-blue-400 hover:text-blue-300 flex items-center gap-2 text-sm"> | |
<i class="fas fa-download"></i> Export Analysis | |
</button> | |
</div> | |
</section> | |
<!-- Stats and insights --> | |
<section class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-10"> | |
<div class="bg-gray-800 p-6 rounded-lg shadow-lg"> | |
<div class="flex items-center gap-3 mb-4"> | |
<div class="bg-blue-600 p-3 rounded-full"> | |
<i class="fas fa-mouse-pointer text-white"></i> | |
</div> | |
<h3 class="font-semibold">Click Hotspots</h3> | |
</div> | |
<div class="space-y-3"> | |
<div> | |
<div class="flex justify-between text-sm mb-1"> | |
<span>Main CTA Button</span> | |
<span>82%</span> | |
</div> | |
<div class="w-full bg-gray-700 rounded-full h-2"> | |
<div class="bg-blue-500 h-2 rounded-full" style="width: 82%"></div> | |
</div> | |
</div> | |
<div> | |
<div class="flex justify-between text-sm mb-1"> | |
<span>Navigation Menu</span> | |
<span>67%</span> | |
</div> | |
<div class="w-full bg-gray-700 rounded-full h-2"> | |
<div class="bg-blue-500 h-2 rounded-full" style="width: 67%"></div> | |
</div> | |
</div> | |
<div> | |
<div class="flex justify-between text-sm mb-1"> | |
<span>Product Images</span> | |
<span>45%</span> | |
</div> | |
<div class="w-full bg-gray-700 rounded-full h-2"> | |
<div class="bg-blue-500 h-2 rounded-full" style="width: 45%"></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="bg-gray-800 p-6 rounded-lg shadow-lg"> | |
<div class="flex items-center gap-3 mb-4"> | |
<div class="bg-green-600 p-3 rounded-full"> | |
<i class="fas fa-mouse text-white"></i> | |
</div> | |
<h3 class="font-semibold">Scroll Depth</h3> | |
</div> | |
<div class="space-y-3"> | |
<div> | |
<div class="flex justify-between text-sm mb-1"> | |
<span>Above the fold</span> | |
<span>100%</span> | |
</div> | |
<div class="w-full bg-gray-700 rounded-full h-2"> | |
<div class="bg-green-500 h-2 rounded-full" style="width: 100%"></div> | |
</div> | |
</div> | |
<div> | |
<div class="flex justify-between text-sm mb-1"> | |
<span>Mid page</span> | |
<span>72%</span> | |
</div> | |
<div class="w-full bg-gray-700 rounded-full h-2"> | |
<div class="bg-green-500 h-2 rounded-full" style="width: 72%"></div> | |
</div> | |
</div> | |
<div> | |
<div class="flex justify-between text-sm mb-1"> | |
<span>Page bottom</span> | |
<span>34%</span> | |
</div> | |
<div class="w-full bg-gray-700 rounded-full h-2"> | |
<div class="bg-green-500 h-2 rounded-full" style="width: 34%"></div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="bg-gray-800 p-6 rounded-lg shadow-lg"> | |
<div class="flex items-center gap-3 mb-4"> | |
<div class="bg-purple-600 p-3 rounded-full"> | |
<i class="fas fa-lightbulb text-white"></i> | |
</div> | |
<h3 class="font-semibold">Key Insights</h3> | |
</div> | |
<ul class="space-y-3"> | |
<li class="flex items-start gap-2"> | |
<i class="fas fa-check text-green-400 mt-1"></i> | |
<span>Top-right CTA button has excellent visibility</span> | |
</li> | |
<li class="flex items-start gap-2"> | |
<i class="fas fa-exclamation-triangle text-yellow-400 mt-1"></i> | |
<span>Product descriptions need more engagement</span> | |
</li> | |
<li class="flex items-start gap-2"> | |
<i class="fas fa-exclamation-circle text-red-400 mt-1"></i> | |
<span>30% of users never scroll past the first section</span> | |
</li> | |
<li class="flex items-start gap-2"> | |
<i class="fas fa-check text-green-400 mt-1"></i> | |
<span>Navigation menu is intuitive and well-used</span> | |
</li> | |
</ul> | |
</div> | |
</section> | |
<!-- Detailed analysis section --> | |
<section class="bg-gray-800 rounded-lg p-6 shadow-lg mb-10"> | |
<div class="flex justify-between items-center mb-6"> | |
<h3 class="text-xl font-semibold">Detailed Analysis</h3> | |
<button class="text-blue-400 hover:text-blue-300 text-sm flex items-center gap-2"> | |
<i class="fas fa-sync-alt"></i> Refresh Data | |
</button> | |
</div> | |
<div class="overflow-x-auto custom-scrollbar"> | |
<table class="w-full text-sm"> | |
<thead> | |
<tr class="border-b border-gray-700 text-left"> | |
<th class="pb-3 pr-6">Element</th> | |
<th class="pb-3 pr-6">Clicks</th> | |
<th class="pb-3 pr-6">Hovers</th> | |
<th class="pb-3 pr-6">Attention (sec)</th> | |
<th class="pb-3 pr-6">Engagement Score</th> | |
<th class="pb-3">Suggestions</th> | |
</tr> | |
</thead> | |
<tbody class="divide-y divide-gray-700"> | |
<tr> | |
<td class="py-3 pr-6 font-medium">Main CTA Button</td> | |
<td class="py-3 pr-6">1,248</td> | |
<td class="py-3 pr-6">2,450</td> | |
<td class="py-3 pr-6">12.4</td> | |
<td class="py-3 pr-6"> | |
<div class="flex items-center gap-2"> | |
<div class="w-20 bg-gray-700 rounded-full h-2"> | |
<div class="bg-green-500 h-2 rounded-full" style="width: 92%"></div> | |
</div> | |
<span>92%</span> | |
</div> | |
</td> | |
<td class="py-3 text-blue-400">No changes needed</td> | |
</tr> | |
<tr> | |
<td class="py-3 pr-6 font-medium">Product Grid</td> | |
<td class="py-3 pr-6">876</td> | |
<td class="py-3 pr-6">1,980</td> | |
<td class="py-3 pr-6">8.2</td> | |
<td class="py-3 pr-6"> | |
<div class="flex items-center gap-2"> | |
<div class="w-20 bg-gray-700 rounded-full h-2"> | |
<div class="bg-yellow-500 h-2 rounded-full" style="width: 68%"></div> | |
</div> | |
<span>68%</span> | |
</div> | |
</td> | |
<td class="py-3 text-yellow-400">Improve image quality</td> | |
</tr> | |
<tr> | |
<td class="py-3 pr-6 font-medium">Footer Links</td> | |
<td class="py-3 pr-6">210</td> | |
<td class="py-3 pr-6">450</td> | |
<td class="py-3 pr-6">3.5</td> | |
<td class="py-3 pr-6"> | |
<div class="flex items-center gap-2"> | |
<div class="w-20 bg-gray-700 rounded-full h-2"> | |
<div class="bg-orange-500 h-2 rounded-full" style="width: 42%"></div> | |
</div> | |
<span>42%</span> | |
</div> | |
</td> | |
<td class="py-3 text-red-400">Consider reorganization</td> | |
</tr> | |
<tr> | |
<td class="py-3 pr-6 font-medium">Testimonial Section</td> | |
<td class="py-3 pr-6">145</td> | |
<td class="py-3 pr-6">780</td> | |
<td class="py-3 pr-6">5.8</td> | |
<td class="py-3 pr-6"> | |
<div class="flex items-center gap-2"> | |
<div class="w-20 bg-gray-700 rounded-full h-2"> | |
<div class="bg-blue-500 h-2 rounded-full" style="width: 58%"></div> | |
</div> | |
<span>58%</span> | |
</div> | |
</td> | |
<td class="py-3 text-blue-400">Add more testimonials</td> | |
</tr> | |
</tbody> | |
</table> | |
</div> | |
</section> | |
</main> | |
<!-- Footer --> | |
<footer class="bg-gray-800 text-gray-400 py-8 mt-12"> | |
<div class="container mx-auto px-4"> | |
<div class="flex flex-col md:flex-row justify-between gap-8 mb-8"> | |
<div class="max-w-xs"> | |
<div class="flex items-center gap-2 mb-4"> | |
<i class="fas fa-fire text-2xl text-orange-400"></i> | |
<h3 class="text-xl font-bold">WCAIG Heatmap</h3> | |
</div> | |
<p class="text-sm"> | |
Advanced heatmap analytics tool to help you understand user behavior and optimize your website's performance. | |
</p> | |
</div> | |
<div> | |
<h4 class="font-semibold text-white mb-3">Product</h4> | |
<ul class="space-y-2"> | |
<li><a href="#" class="hover:text-blue-400 transition text-sm">Features</a></li> | |
<li><a href="#" class="hover:text-blue-400 transition text-sm">Pricing</a></li> | |
<li><a href="#" class="hover:text-blue-400 transition text-sm">Documentation</a></li> | |
<li><a href="#" class="hover:text-blue-400 transition text-sm">Updates</a></li> | |
</ul> | |
</div> | |
<div> | |
<h4 class="font-semibold text-white mb-3">Resources</h4> | |
<ul class="space-y-2"> | |
<li><a href="#" class="hover:text-blue-400 transition text-sm">Blog</a></li> | |
<li><a href="#" class="hover:text-blue-400 transition text-sm">Guides</a></li> | |
<li><a href="#" class="hover:text-blue-400 transition text-sm">Case Studies</a></li> | |
<li><a href="#" class="hover:text-blue-400 transition text-sm">Support</a></li> | |
</ul> | |
</div> | |
<div> | |
<h4 class="font-semibold text-white mb-3">Company</h4> | |
<ul class="space-y-2"> | |
<li><a href="#" class="hover:text-blue-400 transition text-sm">About Us</a></li> | |
<li><a href="#" class="hover:text-blue-400 transition text-sm">Careers</a></li> | |
<li><a href="#" class="hover:text-blue-400 transition text-sm">Contact</a></li> | |
<li><a href="#" class="hover:text-blue-400 transition text-sm">Legal</a></li> | |
</ul> | |
</div> | |
</div> | |
<div class="pt-6 border-t border-gray-700 flex flex-col md:flex-row justify-between items-center gap-4"> | |
<p class="text-sm">© 2023 WCAIG Heatmap Analyzer. All rights reserved.</p> | |
<div class="flex space-x-4"> | |
<a href="#" class="text-gray-400 hover:text-blue-400 transition"><i class="fab fa-twitter"></i></a> | |
<a href="#" class="text-gray-400 hover:text-blue-400 transition"><i class="fab fa-linkedin"></i></a> | |
<a href="#" class="text-gray-400 hover:text-blue-400 transition"><i class="fab fa-github"></i></a> | |
<a href="#" class="text-gray-400 hover:text-blue-400 transition"><i class="fab fa-facebook"></i></a> | |
</div> | |
</div> | |
</div> | |
</footer> | |
<!-- Accessibility Analysis Script --> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ColorContrastChecker.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/hotspot-heatmap.min.js"></script> | |
<script> | |
// Initialize contrast checker with error handling | |
let contrastChecker; | |
try { | |
contrastChecker = new ColorContrastChecker(); | |
} catch (e) { | |
console.error("ColorContrastChecker initialization failed:", e); | |
// Fallback to simple contrast checking logic if library fails | |
contrastChecker = { | |
rgbToYIQ: function(rgb, bg = [255,255,255]) { | |
// Simple contrast ratio calculation | |
const l1 = (0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]) / 255; | |
const l2 = (0.299 * bg[0] + 0.587 * bg[1] + 0.114 * bg[2]) / 255; | |
return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05); | |
} | |
}; | |
} | |
// Heatmap configuration | |
const heatmapConfig = { | |
container: document.getElementById('heatmapCanvas'), | |
radius: 40, | |
visible: true, | |
gradient: { | |
0.4: 'blue', | |
0.6: 'cyan', | |
0.7: 'lime', | |
0.8: 'yellow', | |
1.0: 'red' | |
} | |
}; | |
const heatmap = h337.create(heatmapConfig); | |
let currentImage = null; | |
// Handle image upload and processing | |
document.getElementById('screenshotUpload').addEventListener('change', function(e) { | |
const file = e.target.files[0]; | |
if (!file) return; | |
const statusElement = document.getElementById('uploadStatus'); | |
statusElement.textContent = "Processing image..."; | |
statusElement.classList.remove('hidden', 'text-red-500', 'text-green-500'); | |
const reader = new FileReader(); | |
reader.onload = function(event) { | |
const img = new Image(); | |
img.onload = function() { | |
// Display the uploaded image | |
const canvas = document.getElementById('heatmapCanvas'); | |
const ctx = canvas.getContext('2d'); | |
currentImage = img; | |
// Resize canvas to match image dimensions (maintaining aspect ratio) | |
const maxHeight = window.innerHeight * 0.6; | |
const scale = Math.min(1, maxHeight / img.height); | |
canvas.width = img.width * scale; | |
canvas.height = img.height * scale; | |
// Draw the image | |
ctx.drawImage(img, 0, 0, canvas.width, canvas.height); | |
// Display as overlay in the heatmap container | |
const overlay = document.getElementById('imageOverlay'); | |
overlay.style.backgroundImage = `url(${event.target.result})`; | |
overlay.classList.remove('hidden'); | |
// Generate mock heatmap data (in real scenario this would come from analytics) | |
generateMockHeatmapData(canvas.width, canvas.height); | |
// Analyze for accessibility | |
analyzeImageContrast(event.target.result); | |
statusElement.textContent = "Image processed successfully!"; | |
statusElement.classList.add('text-green-500'); | |
statusElement.classList.remove('hidden'); | |
}; | |
img.onerror = function() { | |
statusElement.textContent = "Error loading image"; | |
statusElement.classList.add('text-red-500'); | |
statusElement.classList.remove('hidden'); | |
}; | |
img.src = event.target.result; | |
}; | |
reader.onerror = function() { | |
statusElement.textContent = "Error reading file"; | |
statusElement.classList.add('text-red-500'); | |
statusElement.classList.remove('hidden'); | |
}; | |
reader.readAsDataURL(file); | |
}); | |
function generateMockHeatmapData(width, height) { | |
const points = []; | |
const maxPoints = 100; | |
// Generate random heat points concentrated in certain areas | |
for (let i = 0; i < maxPoints; i++) { | |
// Create clusters in 3 areas of the image | |
let x, y, intensity; | |
if (i % 3 === 0) { | |
// Top-right cluster (main CTA) | |
x = width * (0.7 + Math.random() * 0.2); | |
y = height * (0.1 + Math.random() * 0.2); | |
intensity = 80 + Math.random() * 20; | |
} else if (i % 3 === 1) { | |
// Middle cluster (content) | |
x = width * (0.4 + Math.random() * 0.4); | |
y = height * (0.4 + Math.random() * 0.3); | |
intensity = 50 + Math.random() * 30; | |
} else { | |
// Scattered points | |
x = Math.random() * width; | |
y = height * (0.6 + Math.random() * 0.3); | |
intensity = 20 + Math.random() * 30; | |
} | |
points.push({ | |
x: x, | |
y: y, | |
value: intensity | |
}); | |
} | |
// Set the heatmap data | |
heatmap.setData({ | |
max: 100, | |
data: points | |
}); | |
} | |
// Function to analyze image for contrast | |
function analyzeImageContrast(imageData) { | |
// This would use a real image analysis library in production | |
// For this demo, we'll generate mock data | |
const gridContainer = document.getElementById('contrastGrid'); | |
gridContainer.innerHTML = ''; | |
// Generate mock contrast grid | |
for (let i = 0; i < 24; i++) { | |
const randomRatio = Math.random() * 10; // 0-10 | |
let status, bgColor; | |
if (randomRatio >= 7) { | |
status = 'AAA'; | |
bgColor = 'bg-green-500'; | |
} else if (randomRatio >= 4.5) { | |
status = 'AA'; | |
bgColor = 'bg-blue-500'; | |
} else if (randomRatio >= 3) { | |
status = 'Minimum'; | |
bgColor = 'bg-yellow-500'; | |
} else { | |
status = 'Fail'; | |
bgColor = 'bg-red-500'; | |
} | |
gridContainer.innerHTML += ` | |
<div class="${bgColor} h-8 rounded-sm relative group" | |
data-ratio="${randomRatio.toFixed(2)}:1" | |
data-status="${status}"> | |
<div class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-30 transition"></div> | |
<div class="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition"> | |
<span class="text-xs font-bold bg-black bg-opacity-70 px-1 py-0.5 rounded">${randomRatio.toFixed(2)}:1</span> | |
</div> | |
</div> | |
`; | |
} | |
document.getElementById('contrastAnalysis').classList.remove('hidden'); | |
} | |
// Handle real image upload and analysis | |
document.getElementById('screenshotUpload').addEventListener('change', function(e) { | |
const file = e.target.files[0]; | |
if (!file) return; | |
const reader = new FileReader(); | |
reader.onload = function(event) { | |
const img = new Image(); | |
img.onload = function() { | |
// Create canvas to analyze portions of the image | |
const canvas = document.createElement('canvas'); | |
const ctx = canvas.getContext('2d'); | |
const blockSize = 32; // 32px blocks | |
// Calculate grid dimensions | |
const gridWidth = Math.ceil(img.width / blockSize); | |
const gridHeight = Math.ceil(img.height / blockSize); | |
// Prepare the contrast grid display | |
const gridContainer = document.getElementById('contrastGrid'); | |
gridContainer.innerHTML = ''; | |
gridContainer.style.gridTemplateColumns = `repeat(${gridWidth}, minmax(0, 1fr))`; | |
// Analyze each block | |
for (let y = 0; y < gridHeight; y++) { | |
for (let x = 0; x < gridWidth; x++) { | |
// Draw and get block image data | |
canvas.width = blockSize; | |
canvas.height = blockSize; | |
ctx.drawImage( | |
img, | |
x * blockSize, y * blockSize, blockSize, blockSize, | |
0, 0, blockSize, blockSize | |
); | |
// Get average color of the block | |
const imageData = ctx.getImageData(0, 0, blockSize, blockSize); | |
const rgb = getAverageColor(imageData.data); | |
// Calculate contrast against common text colors (black/white) | |
const contrastWhite = contrastChecker.rgbToYIQ(rgb); | |
const contrastBlack = contrastChecker.rgbToYIQ(rgb, [0, 0, 0]); | |
const higherContrast = Math.max(contrastWhite, contrastBlack); | |
// Determine WCAG compliance | |
let status, bgColor; | |
if (higherContrast >= 7) { | |
status = 'AAA'; | |
bgColor = 'bg-green-500'; | |
} else if (higherContrast >= 4.5) { | |
status = 'AA'; | |
bgColor = 'bg-blue-500'; | |
} else if (higherContrast >= 3) { | |
status = 'Minimum'; | |
bgColor = 'bg-yellow-500'; | |
} else { | |
status = 'Fail'; | |
bgColor = 'bg-red-500'; | |
} | |
// Add block to grid | |
gridContainer.innerHTML += ` | |
<div class="${bgColor} h-8 rounded-sm relative group" | |
data-ratio="${higherContrast.toFixed(2)}:1" | |
data-status="${status}"> | |
<div class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-30 transition"></div> | |
<div class="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition"> | |
<span class="text-xs font-bold bg-black bg-opacity-70 px-1 py-0.5 rounded">${higherContrast.toFixed(2)}:1</span> | |
</div> | |
</div> | |
`; | |
} | |
} | |
document.getElementById('contrastAnalysis').classList.remove('hidden'); | |
// Show success message | |
showNotification('Image uploaded and analyzed for accessibility!', 'green'); | |
}; | |
img.src = event.target.result; | |
}; | |
reader.readAsDataURL(file); | |
}); | |
// Helper function to get average color of an image block | |
function getAverageColor(data) { | |
let r = 0, g = 0, b = 0; | |
const count = data.length / 4; | |
for (let i = 0; i < data.length; i += 4) { | |
r += data[i]; | |
g += data[i + 1]; | |
b += data[i + 2]; | |
} | |
return [ | |
Math.round(r / count), | |
Math.round(g / count), | |
Math.round(b / count) | |
]; | |
} | |
// Show notification | |
function showNotification(message, color) { | |
const notification = document.createElement('div'); | |
notification.className = `fixed bottom-4 right-4 bg-${color}-600 text-white px-4 py-2 rounded-md shadow-lg animate-fade-in`; | |
notification.innerHTML = `<i class="fas fa-check-circle mr-2"></i> ${message}`; | |
document.body.appendChild(notification); | |
setTimeout(() => notification.remove(), 3000); | |
} | |
<!-- Mobile menu toggle script --> | |
document.getElementById('mobileMenuBtn').addEventListener('click', function() { | |
const menu = document.getElementById('mobileMenu'); | |
menu.classList.toggle('hidden'); | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment