diff --git a/images-gallery.js b/images-gallery.js
new file mode 100644
index 0000000..9994b7a
--- /dev/null
+++ b/images-gallery.js
@@ -0,0 +1,172 @@
+/**
+ * Images Gallery - Display all loaded images with optimization analysis
+ */
+
+async function init() {
+ const params = new URLSearchParams(window.location.search);
+ const testId = params.get('id');
+
+ if (!testId) {
+ document.getElementById('imagesGrid').innerHTML =
+ '
Error: No test ID provided
';
+ return;
+ }
+
+ try {
+ const response = await fetch(`/reports/${testId}.har.json`);
+ if (!response.ok) throw new Error('HAR data not found');
+
+ const harData = await response.json();
+ renderImageGallery(harData);
+ } catch (error) {
+ document.getElementById('imagesGrid').innerHTML =
+ `Error loading images: ${error.message}
`;
+ }
+}
+
+function renderImageGallery(harData) {
+ // Filter for image requests
+ const images = harData.entries.filter(entry =>
+ entry.resourceType === 'Image' ||
+ entry.mimeType?.startsWith('image/') ||
+ entry.url.match(/\.(jpg|jpeg|png|gif|webp|svg|ico|bmp)($|\?)/i)
+ );
+
+ if (images.length === 0) {
+ document.getElementById('imagesGrid').innerHTML =
+ 'No images found in this page load.
';
+ return;
+ }
+
+ // Calculate summary stats
+ const totalSize = images.reduce((sum, img) => sum + img.size.transferSize, 0);
+ const unoptimized = images.filter(img => analyzeOptimization(img).level === 'error').length;
+ const avgSize = totalSize / images.length;
+
+ // Render summary
+ document.getElementById('summaryStats').innerHTML = `
+
+
${images.length}
+
Total Images
+
+
+
${formatBytes(totalSize)}
+
Total Size
+
+
+
${formatBytes(avgSize)}
+
Average Size
+
+
+
${unoptimized}
+
Needs Optimization
+
+ `;
+
+ // Render image cards
+ let html = '';
+ images.forEach(image => {
+ const optimization = analyzeOptimization(image);
+ const filename = extractFilename(image.url);
+ const format = getImageFormat(image);
+
+ html += `
+
+
+

+
🖼️
+
+
+
${filename}
+
+
+ Format
+ ${format}
+
+
+ Size
+ ${formatBytes(image.size.transferSize)}
+
+
+ Compression
+ ${(image.size.compressionRatio * 100).toFixed(0)}%
+
+
+ Load Time
+ ${image.timing.total.toFixed(0)}ms
+
+
+
+ ${optimization.message}
+
+
+
+ `;
+ });
+
+ document.getElementById('imagesGrid').innerHTML = html;
+}
+
+function analyzeOptimization(image) {
+ const size = image.size.transferSize;
+ const format = getImageFormat(image);
+
+ // Check if modern format (WebP, AVIF)
+ if (format === 'WebP' || format === 'AVIF') {
+ return { level: 'good', message: '✓ Modern format' };
+ }
+
+ // Check size thresholds
+ if (size > 500 * 1024) { // > 500KB
+ return { level: 'error', message: '⚠️ Very large - optimize!' };
+ }
+
+ if (size > 200 * 1024) { // > 200KB
+ return { level: 'warning', message: '⚠️ Could be smaller' };
+ }
+
+ // Check if SVG (good for icons/logos)
+ if (format === 'SVG') {
+ return { level: 'good', message: '✓ Vector (scalable)' };
+ }
+
+ return { level: 'good', message: '✓ Optimized' };
+}
+
+function getImageFormat(image) {
+ const url = image.url.toLowerCase();
+ const mime = image.mimeType?.toLowerCase();
+
+ if (mime?.includes('webp') || url.includes('.webp')) return 'WebP';
+ if (mime?.includes('avif') || url.includes('.avif')) return 'AVIF';
+ if (mime?.includes('svg') || url.includes('.svg')) return 'SVG';
+ if (mime?.includes('png') || url.includes('.png')) return 'PNG';
+ if (mime?.includes('gif') || url.includes('.gif')) return 'GIF';
+ if (mime?.includes('jpeg') || mime?.includes('jpg') || url.match(/\.jpe?g/)) return 'JPEG';
+ if (url.includes('.ico')) return 'ICO';
+ if (url.includes('.bmp')) return 'BMP';
+
+ return 'Unknown';
+}
+
+function extractFilename(url) {
+ try {
+ const urlObj = new URL(url);
+ const pathname = urlObj.pathname;
+ const filename = pathname.split('/').pop() || pathname;
+ return filename.length > 40 ? filename.substring(0, 37) + '...' : filename;
+ } catch {
+ return url.substring(0, 40) + '...';
+ }
+}
+
+function formatBytes(bytes) {
+ if (bytes === 0) return '0 B';
+ const k = 1024;
+ const sizes = ['B', 'KB', 'MB', 'GB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+}
+
+// Initialize on page load
+document.addEventListener('DOMContentLoaded', init);
diff --git a/images.html b/images.html
new file mode 100644
index 0000000..4e56f37
--- /dev/null
+++ b/images.html
@@ -0,0 +1,160 @@
+
+
+
+
+
+ Image Gallery - Web Page Performance Test
+
+
+
+
+
+
Page Images Gallery
+
All images loaded by this page with optimization analysis
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/index.html b/index.html
index 526665d..2a59220 100644
--- a/index.html
+++ b/index.html
@@ -100,9 +100,12 @@