Skip to content

Instantly share code, notes, and snippets.

@dexit
Created June 12, 2025 12:18
Show Gist options
  • Save dexit/4f198259d1dd100ed63e1db672529bcd to your computer and use it in GitHub Desktop.
Save dexit/4f198259d1dd100ed63e1db672529bcd to your computer and use it in GitHub Desktop.
heatmap page
<!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">&copy; 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