#!/usr/bin/env node
/**
* Mobile Responsiveness Audit
*
* Checks viewport configuration and touch target sizes (WCAG 2.5.5)
*
* Copyright 2025 Tractatus Project
* Licensed under Apache License 2.0
*/
const http = require('http');
const fs = require('fs');
const path = require('path');
const colors = {
reset: '\x1b[0m',
bright: '\x1b[1m',
green: '\x1b[32m',
yellow: '\x1b[33m',
red: '\x1b[31m',
cyan: '\x1b[36m'
};
function log(message, color = 'reset') {
console.log(`${colors[color]}${message}${colors.reset}`);
}
function success(message) {
log(` ✓ ${message}`, 'green');
}
function warning(message) {
log(` ⚠ ${message}`, 'yellow');
}
function error(message) {
log(` ✗ ${message}`, 'red');
}
// Pages to test
const pages = [
{ name: 'Homepage', url: 'http://localhost:9000/' },
{ name: 'Researcher', url: 'http://localhost:9000/researcher.html' },
{ name: 'Implementer', url: 'http://localhost:9000/implementer.html' },
{ name: 'Advocate', url: 'http://localhost:9000/advocate.html' },
{ name: 'About', url: 'http://localhost:9000/about.html' },
{ name: 'Values', url: 'http://localhost:9000/about/values.html' },
{ name: 'Docs', url: 'http://localhost:9000/docs.html' },
{ name: 'Media Inquiry', url: 'http://localhost:9000/media-inquiry.html' },
{ name: 'Case Submission', url: 'http://localhost:9000/case-submission.html' }
];
/**
* Fetch page HTML
*/
function fetchPage(url) {
return new Promise((resolve, reject) => {
http.get(url, (res) => {
let data = '';
res.on('data', (chunk) => data += chunk);
res.on('end', () => resolve(data));
}).on('error', reject);
});
}
/**
* Check viewport meta tag
*/
function checkViewport(html) {
const viewportMatch = html.match(/]*name="viewport"[^>]*>/i);
if (!viewportMatch) {
return { exists: false, content: null, valid: false };
}
const contentMatch = viewportMatch[0].match(/content="([^"]*)"/i);
const content = contentMatch ? contentMatch[1] : null;
// Check for proper responsive viewport
const hasWidth = content?.includes('width=device-width');
const hasInitialScale = content?.includes('initial-scale=1');
return {
exists: true,
content,
valid: hasWidth && hasInitialScale
};
}
/**
* Analyze interactive elements for touch targets
*/
function analyzeTouchTargets(html) {
const issues = [];
// Check for small buttons (buttons should have min height/width via Tailwind)
const buttons = html.match(/