← back to Quadrille Showroom
Age View Phase-5 gate: fix under-spec type sizes on Senior/Elder bands + add re-runnable verifier
1ea5d2b3013b0c3915da54013904c93ecb205022 · 2026-07-01 09:40:04 -0700 · Steve
Ran the spec's HARD senior-usability gate (APCA + target-size + type-size) measured
against the real BANDS tokens. APCA contrast passed everywhere with wide margins, but
the two mid-senior bands were short on type size once the 65→85 requirement is
interpolated to the oldest age each band serves:
• Senior (≤70): body 20→22, head 30→34
• Elder (≤80): body 22→26, head 32→42
Re-ran the gate post-edit (validation-loop): all 10 bands PASS. Added
scripts/verify-ageview-gate.mjs (npm run verify-ageview-gate, exit 1 on fail).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Files touched
M package.jsonM public/js/ageview.jsA scripts/verify-ageview-gate.mjs
Diff
commit 1ea5d2b3013b0c3915da54013904c93ecb205022
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Jul 1 09:40:04 2026 -0700
Age View Phase-5 gate: fix under-spec type sizes on Senior/Elder bands + add re-runnable verifier
Ran the spec's HARD senior-usability gate (APCA + target-size + type-size) measured
against the real BANDS tokens. APCA contrast passed everywhere with wide margins, but
the two mid-senior bands were short on type size once the 65→85 requirement is
interpolated to the oldest age each band serves:
• Senior (≤70): body 20→22, head 30→34
• Elder (≤80): body 22→26, head 32→42
Re-ran the gate post-edit (validation-loop): all 10 bands PASS. Added
scripts/verify-ageview-gate.mjs (npm run verify-ageview-gate, exit 1 on fail).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
package.json | 3 ++-
public/js/ageview.js | 4 ++--
scripts/verify-ageview-gate.mjs | 32 ++++++++++++++++++++++++++++++++
3 files changed, 36 insertions(+), 3 deletions(-)
diff --git a/package.json b/package.json
index a945d3a..56fe27e 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,8 @@
"start": "node server.js",
"build-data": "node scripts/build-showroom-data.js",
"gen-assets": "node scripts/gen-assets.js",
- "verify-carousel": "node scripts/verify-carousel.mjs"
+ "verify-carousel": "node scripts/verify-carousel.mjs",
+ "verify-ageview-gate": "node scripts/verify-ageview-gate.mjs"
},
"license": "ISC",
"description": "The Quadrille House — 3D Endless Wings Wallcovering Showroom (China Seas)",
diff --git a/public/js/ageview.js b/public/js/ageview.js
index 52fc37d..d590333 100644
--- a/public/js/ageview.js
+++ b/public/js/ageview.js
@@ -75,10 +75,10 @@ const BANDS = [
font:"'Source Serif Pro', Georgia, serif", body:16, head:24, btn:48, gap:18, opaque:false,
bg:'#f8f6f1', panel:'#ffffff', fg:'#1c1917', cta:'#6b1d07', ctaFg:'#ffffff', border:'#cdc9c2', muted:'#5f584d' },
{ max:70, id:'senior', name:'Senior', tag:'High contrast, large text, big targets.',
- font:"'Atkinson Hyperlegible', Helvetica, Arial, sans-serif", body:20, head:30, btn:48, gap:20, opaque:true,
+ font:"'Atkinson Hyperlegible', Helvetica, Arial, sans-serif", body:22, head:34, btn:48, gap:20, opaque:true,
bg:'#ffffff', panel:'#ffffff', fg:'#000000', cta:'#003a78', ctaFg:'#ffffff', border:'#000000', muted:'#333333' },
{ max:80, id:'elder', name:'Elder', tag:'One idea per screen — maximum legibility.',
- font:"'Atkinson Hyperlegible', Helvetica, Arial, sans-serif", body:22, head:32, btn:52, gap:24, opaque:true,
+ font:"'Atkinson Hyperlegible', Helvetica, Arial, sans-serif", body:26, head:42, btn:52, gap:24, opaque:true,
bg:'#ffffff', panel:'#ffffff', fg:'#000000', cta:'#003a78', ctaFg:'#ffffff', border:'#000000', muted:'#2a2a2a' },
{ max:85, id:'legacy', name:'Legacy', tag:'Just the action — yellow on black, biggest type.',
font:"'Atkinson Hyperlegible', Helvetica, Arial, sans-serif", body:28, head:44, btn:56, gap:28, opaque:true,
diff --git a/scripts/verify-ageview-gate.mjs b/scripts/verify-ageview-gate.mjs
new file mode 100644
index 0000000..df2a89e
--- /dev/null
+++ b/scripts/verify-ageview-gate.mjs
@@ -0,0 +1,32 @@
+// verify-ageview-gate.mjs — Phase-5 HARD senior-usability gate for the Age View.
+// Measures APCA Lc (body≥60, CTA≥75), target-size, and type-size against the actual
+// BANDS tokens in public/js/ageview.js. Type-size requirement is interpolated across the
+// spec anchors 65→85 (evaluated at each band's oldest served age). Exit 1 on any fail.
+import fs from "fs";
+const src = fs.readFileSync("public/js/ageview.js","utf8");
+function hexToRgb(h){ h=h.replace('#',''); if(h.length===3) h=h.split('').map(c=>c+c).join(''); return [parseInt(h.slice(0,2),16),parseInt(h.slice(2,4),16),parseInt(h.slice(4,6),16)]; }
+function sRGBtoY([r,g,b]){ return 0.2126729*Math.pow(r/255,2.4)+0.7151522*Math.pow(g/255,2.4)+0.0721750*Math.pow(b/255,2.4); }
+function apcaLc(t,bg){const BlkThrs=0.022,BlkClmp=1.414,LoConThresh=0.1,Scale=1.14,LoOffset=0.027;let ty=sRGBtoY(hexToRgb(t)),by=sRGBtoY(hexToRgb(bg));if(ty<BlkThrs)ty+=Math.pow(BlkThrs-ty,BlkClmp);if(by<BlkThrs)by+=Math.pow(BlkThrs-by,BlkClmp);if(Math.abs(by-ty)<0.0005)return 0;if(by>ty){const S=(Math.pow(by,0.56)-Math.pow(ty,0.57))*Scale;return S<LoConThresh?0:Math.round((S-LoOffset)*100);}const S=(Math.pow(by,0.65)-Math.pow(ty,0.62))*Scale;return S>-LoConThresh?0:Math.round((S+LoOffset)*100);}
+const bi=src.indexOf("const BANDS"), be=src.indexOf("];",bi)+1;
+const BANDS=eval("("+src.slice(bi+src.slice(bi).indexOf("["),be)+")");
+// spec anchors 65→85 linear; below 65 use base floors
+const lerp=(a,x0,y0,x1,y1)=>Math.ceil(y0+(a-x0)/(x1-x0)*(y1-y0));
+const reqBodyPx=a=>a<65?12:lerp(Math.min(a,85),65,20,85,28);
+const reqHeadPx=a=>a<65?18:lerp(Math.min(a,85),65,30,85,44);
+const reqBtnPx =a=>a>=85?56:a>=65?48:44;
+console.log("age | opq | bodyLc | ctaLc | body(req) | head(req) | btn(req) | VERDICT");
+let fails=0;
+for(const b of BANDS){const a=b.max;
+ const bodyLc=Math.abs(apcaLc(b.fg,b.panel)), ctaLc=Math.abs(apcaLc(b.ctaFg,b.cta));
+ const rb=reqBodyPx(a), rh=reqHeadPx(a), rk=reqBtnPx(a);
+ const c=[];
+ if(bodyLc<60)c.push("BODY-APCA "+bodyLc);
+ if(ctaLc<75)c.push("CTA-APCA "+ctaLc);
+ if(a>=65&&!b.opaque)c.push("NOT-OPAQUE");
+ if(b.btn<rk)c.push(`BTN ${b.btn}<${rk}`);
+ if(b.body<rb)c.push(`BODY ${b.body}<${rb}`);
+ if(b.head<rh)c.push(`HEAD ${b.head}<${rh}`);
+ const ok=c.length===0; if(!ok)fails++;
+ console.log(`${String(a).padStart(3)} | ${b.opaque?"Y":"·"} | ${String(bodyLc).padStart(4)} | ${String(ctaLc).padStart(4)} | ${b.body}(${rb}) | ${b.head}(${rh}) | ${b.btn}(${rk}) | ${ok?"PASS":"FAIL → "+c.join(", ")}`);
+}
+if(fails){console.log("\n❌ "+fails+" band(s) FAIL the Phase-5 senior/APCA gate"); process.exit(1);} else {console.log("\n✅ ALL "+BANDS.length+" bands PASS the measured Phase-5 gate (APCA + target-size + type-size)");}
← e034910 auto-save: 2026-07-01T09:10:58 (1 files) — public/js/dock.js
·
back to Quadrille Showroom
·
Age View Phase-5.3: cross-engine render gate (Chromium/Firef d4295bd →