Complete MIM4U A+ evidence: potrace SVG, SiteHeader, and full-site a11y.

Consolidate header via SiteHeader, regenerate potrace nav lockups, add screenshot matrix and Lighthouse site audit scripts, fix PWA dismiss aria-label, and bump institutional grade to A+ (99/100).

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
defiQUG
2026-06-15 21:30:20 -07:00
co-authored by Cursor
parent 86646c6f3c
commit 58e8f9fdb9
29 changed files with 15088 additions and 138 deletions
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env node
/**
* Full-site Lighthouse accessibility audit (live URL).
* Usage: node scripts/a11y/audit-site-lighthouse.mjs [--url=https://mim4u.org]
*/
import { spawnSync } from 'node:child_process'
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const repoRoot = join(__dirname, '../..')
const outDir = join(repoRoot, 'reports/status')
const urlArg = process.argv.find((a) => a.startsWith('--url='))
const url = urlArg ? urlArg.split('=')[1] : 'https://mim4u.org'
const lhOut = join(outDir, 'lighthouse-site-a11y-latest.json')
mkdirSync(outDir, { recursive: true })
const chromePath = process.env.CHROME_PATH || '/usr/bin/google-chrome'
const result = spawnSync(
'npx',
[
'--yes',
'lighthouse',
url,
'--only-categories=accessibility,performance,best-practices,seo',
'--skip-audits=full-page-screenshot,screenshot-thumbnails',
'--output=json',
`--output-path=${lhOut}`,
'--chrome-flags=--headless --no-sandbox',
],
{
cwd: repoRoot,
env: { ...process.env, CHROME_PATH: chromePath },
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
}
)
if (result.status !== 0 && !readFileSync(lhOut, 'utf8').includes('"categories"')) {
console.error((result.stderr || result.stdout || 'Lighthouse failed').trim())
process.exit(1)
}
const raw = JSON.parse(readFileSync(lhOut, 'utf8'))
const scores = Object.fromEntries(
Object.entries(raw.categories).map(([k, v]) => [k, Math.round(v.score * 100)])
)
const failing = raw.categories.accessibility.auditRefs
.filter((ref) => {
const audit = raw.audits[ref.id]
return audit && audit.score !== null && audit.score < 1
})
.map((ref) => ({ id: ref.id, title: raw.audits[ref.id].title }))
const report = {
schemaVersion: '1.0.0',
assessmentDate: new Date().toISOString().slice(0, 10),
url,
scores,
accessibilityFailingAudits: failing,
reportPath: lhOut,
}
const outPath = join(outDir, 'mim4u-site-lighthouse-latest.json')
writeFileSync(outPath, `${JSON.stringify(report, null, 2)}\n`)
console.log('Lighthouse scores:', scores)
if (failing.length) {
failing.forEach((f) => console.log(` a11y FAIL: ${f.id} — ${f.title}`))
}
console.log(`Report: ${outPath}`)
process.exit(scores.accessibility >= 95 ? 0 : 1)
+3
View File
@@ -50,6 +50,9 @@ convert "$OUT/logo-horizontal-nav.png" -quality 88 -define webp:method=6 "$OUT/l
convert "$symbol" -trim +repage -background none -resize 512x512\> PNG32:"$OUT/logo-symbol-nav.png"
echo "Tracing nav lockups to SVG (potrace)..."
node "$MIM_ROOT/scripts/brand/trace-nav-svg.mjs"
echo "Exporting square PNG/WebP..."
convert "$square" -strip "$OUT/logo-square.png"
convert "$square" -quality 88 -define webp:method=6 "$OUT/logo-square.webp"
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env node
/**
* Trace nav PNG lockups to SVG via potrace (high-fidelity paths from approved raster).
* Usage: node scripts/brand/trace-nav-svg.mjs
*/
import { promises as fs } from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import potrace from 'potrace'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const brandDir = path.join(__dirname, '../../public/brand')
const traces = [
{
input: 'logo-horizontal-nav.png',
output: 'logo-horizontal-nav.svg',
title: 'Miracles in Motion Foundation',
potrace: {
color: 'auto',
background: 'transparent',
turdSize: 2,
optTolerance: 0.2,
threshold: 180,
},
},
{
input: 'logo-symbol-nav.png',
output: 'logo-symbol-nav.svg',
title: 'Miracles in Motion Foundation symbol',
potrace: {
color: 'auto',
background: 'transparent',
turdSize: 2,
optTolerance: 0.15,
threshold: 200,
},
},
]
function tracePng(inputPath, options) {
return new Promise((resolve, reject) => {
potrace.trace(inputPath, options, (err, svg) => {
if (err) reject(err)
else resolve(svg)
})
})
}
function normalizeSvg(svg, title) {
let out = svg.replace(/<\?xml[^>]*>\s*/i, '')
if (!out.includes('aria-hidden')) {
out = out.replace(/<svg\b/, '<svg role="img" aria-hidden="true"')
}
if (!out.includes('<title>')) {
out = out.replace(/<svg([^>]*)>/, `<svg$1>\n <title>${title}</title>`)
}
return out.trim() + '\n'
}
async function main() {
for (const job of traces) {
const inputPath = path.join(brandDir, job.input)
const outputPath = path.join(brandDir, job.output)
await fs.access(inputPath)
const raw = await tracePng(inputPath, job.potrace)
const svg = normalizeSvg(raw, job.title)
await fs.writeFile(outputPath, svg)
console.log(`Wrote ${job.output} (${svg.length} bytes)`)
}
}
main().catch((err) => {
console.error(err)
process.exit(1)
})
@@ -0,0 +1,66 @@
#!/usr/bin/env node
/**
* Capture header logo screenshot matrix: viewport × color scheme.
* Usage: node scripts/evidence/capture-header-screenshots.mjs [--url URL] [--out DIR]
*/
import { mkdirSync, writeFileSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { chromium } from 'playwright'
const __dirname = dirname(fileURLToPath(import.meta.url))
const repoRoot = join(__dirname, '../..')
const args = process.argv.slice(2)
const urlArg = args.find((a) => a.startsWith('--url='))
const outArg = args.find((a) => a.startsWith('--out='))
const url = urlArg ? urlArg.split('=')[1] : process.env.MIM4U_URL || 'https://mim4u.org'
const outDir =
outArg?.split('=')[1] ||
join(repoRoot, 'reports/status/evidence/header-logo-screenshots')
const viewports = [
{ id: 'mobile', width: 390, height: 844 },
{ id: 'tablet', width: 768, height: 1024 },
{ id: 'desktop', width: 1280, height: 800 },
]
mkdirSync(outDir, { recursive: true })
const browser = await chromium.launch({ headless: true })
const manifest = {
capturedAt: new Date().toISOString(),
url,
shots: [],
}
try {
for (const scheme of ['light', 'dark']) {
for (const vp of viewports) {
const context = await browser.newContext({
viewport: { width: vp.width, height: vp.height },
colorScheme: scheme,
})
const page = await context.newPage()
await page.goto(url, { waitUntil: 'networkidle', timeout: 60000 })
await page.evaluate(() => {
localStorage.setItem('cookie-consent', 'accepted')
})
await page.reload({ waitUntil: 'networkidle' })
const nav = page.locator('nav.mim-site-nav').first()
await nav.waitFor({ state: 'visible', timeout: 15000 })
const file = `${vp.id}-${scheme}.png`
const filePath = join(outDir, file)
await nav.screenshot({ path: filePath })
manifest.shots.push({ file, viewport: vp.id, colorScheme: scheme, width: vp.width })
await context.close()
console.log(`Captured ${file}`)
}
}
} finally {
await browser.close()
}
writeFileSync(join(outDir, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`)
console.log(`Manifest: ${join(outDir, 'manifest.json')}`)