[object Object]

← back to Quadrille Showroom

Age View bar legibility: fix invisible readout text on light-panel bands + add per-band bar-legibility gate

67caf4d47d7dd80c719722e3ed27b88a2af9dd14 · 2026-06-29 16:39:30 -0700 · Steve

Files touched

Diff

commit 67caf4d47d7dd80c719722e3ed27b88a2af9dd14
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Jun 29 16:39:30 2026 -0700

    Age View bar legibility: fix invisible readout text on light-panel bands + add per-band bar-legibility gate
---
 5x/loop-ledger.md                 |  1 +
 public/js/ageview.js              | 26 ++++++++++++++++++------
 scripts/verify-ageview-phase5.mjs | 42 +++++++++++++++++++++++++++++++++++++--
 3 files changed, 61 insertions(+), 8 deletions(-)

diff --git a/5x/loop-ledger.md b/5x/loop-ledger.md
index 09ecfd4..7c81c3b 100644
--- a/5x/loop-ledger.md
+++ b/5x/loop-ledger.md
@@ -3,3 +3,4 @@
 - iter1 (2026-06-29): FPS gate FAIL root-caused — phase5 sampler caught the morph-rebuild transient window (band 65 min=54). Fixed sampler to discard the band-change-straddling counter window + dedup per distinct window + 1200ms settle; floor unchanged at 55. All bands now steady-state 67-74 min. All 4 verifiers GREEN.
 - iter2 (2026-06-29): Backlog (a) DONE — new scripts/verify-ageview-envmorph.mjs MEASURES real rendered wall-luminance for the setEnvForAge morph (was only asserted by intensity scalars). Drives age 35 vs 85 on real GPU, samples pure-plaster wall regions: senior is +6.56 luminance brighter (morph proven to change the render), lower-glare hot-pixel frac non-increasing, OFF restores exact baseline (Δ0.00, avatarAge 28). PASS.
 - iter3 (2026-06-29): Backlog (c) DONE — extended verify-walls-on-next.mjs with an Age-View-ON pass (senior band 75). Paging Next under Age View still clads all 4 walls, 0 console errors; locks the env+avatar-morph × wall-clad interaction as a regression guard. All 5 verifiers GREEN.
+- iter4 (2026-06-29): Backlog (c) DONE + REAL DEFECT FIXED — Age View slider bar text was INVISIBLE at tween/connoisseur/tastemaker bands (age#/band-name Lc≈0: dark band-tuned text on the bar's dark glass). Fixed ageview.js to render the bar readout (age#/band/tag/thumb) in guaranteed-light vars on non-senior bands, falling back to the band's gated colours only when the bar is opaque (senior). Added a per-band BAR-legibility gate to phase5 (was 85-only) + an FPS warm-up apply so band-12 isn't contaminated by the one-time Age-View-activation transient. All bands now age#Lc≥69 band≥101 tag≥74. All 5 verifiers GREEN.
diff --git a/public/js/ageview.js b/public/js/ageview.js
index 708ed0f..52fc37d 100644
--- a/public/js/ageview.js
+++ b/public/js/ageview.js
@@ -120,19 +120,33 @@ function injectChrome() {
   body.age-opaque #ageview-bar { backdrop-filter:none; background:var(--av-panel); }
   #ageview-bar.hidden { display:none; }
   #av-readout { text-align:center; min-width:120px; }
-  #av-age { font-weight:800; font-size:max(28px, var(--av-head)); line-height:1; color:var(--av-cta); }
-  body.age-legacy #av-age { color:var(--av-cta); }
-  #av-band { font-size:max(15px, var(--av-body)); font-weight:700; letter-spacing:.4px; margin-top:3px; color:var(--av-fg); }
+  /* The Age View bar shows a DARK glass panel for every NON-senior band (the
+     senior/elder/legacy bands override it to their own opaque panel). So the bar's
+     readout text must NOT inherit the band's --av-cta / --av-fg — several bands
+     (tween indigo, connoisseur/tastemaker dark-maroon over their OWN light panel)
+     carry DARK text colours that vanish on the dark glass (age#/band Lc≈0, found
+     2026-06-29). Bar text uses guaranteed-light readout vars; only when the bar is
+     itself opaque (senior bands) do we fall back to the band's real colours, which
+     are gated by the senior APCA check. */
+  body.ageview-on { --av-bar-num:#c9b6ff; --av-bar-fg:#f2f5fb; --av-bar-muted:#c2cad8; --av-bar-thumb:#ffffff; }
+  #av-age { font-weight:800; font-size:max(28px, var(--av-head)); line-height:1; color:var(--av-bar-num); }
+  #av-band { font-size:max(15px, var(--av-body)); font-weight:700; letter-spacing:.4px; margin-top:3px; color:var(--av-bar-fg); }
+  /* Senior bands: bar is opaque on the band's own panel → use the band's gated colours. */
+  body.age-opaque #av-age  { color:var(--av-cta); }
+  body.age-opaque #av-band { color:var(--av-fg); }
   /* Slider thumb/track gated for the elder band — large hit target + bright track. */
   #av-slider { -webkit-appearance:none; appearance:none; height:14px; border-radius:8px; min-width:300px; flex:1;
     background:var(--av-cta); outline:none; cursor:pointer; }
   #av-slider::-webkit-slider-thumb { -webkit-appearance:none; appearance:none; width:34px; height:34px; border-radius:50%;
-    background:var(--av-fg); border:3px solid var(--av-cta); cursor:pointer; }
-  #av-slider::-moz-range-thumb { width:34px; height:34px; border-radius:50%; background:var(--av-fg); border:3px solid var(--av-cta); cursor:pointer; }
+    background:var(--av-bar-thumb); border:3px solid var(--av-cta); cursor:pointer; }
+  #av-slider::-moz-range-thumb { width:34px; height:34px; border-radius:50%; background:var(--av-bar-thumb); border:3px solid var(--av-cta); cursor:pointer; }
+  body.age-opaque #av-slider::-webkit-slider-thumb { background:var(--av-fg); }
+  body.age-opaque #av-slider::-moz-range-thumb { background:var(--av-fg); }
   body.age-opaque #av-slider { height:18px; }
   body.age-opaque #av-slider::-webkit-slider-thumb { width:40px; height:40px; }
   body.age-opaque #av-slider::-moz-range-thumb { width:40px; height:40px; }
-  #av-tag { flex-basis:100%; text-align:center; font-size:max(15px, var(--av-body)); color:var(--av-muted); margin-top:2px; }
+  #av-tag { flex-basis:100%; text-align:center; font-size:max(15px, var(--av-body)); color:var(--av-bar-muted); margin-top:2px; }
+  body.age-opaque #av-tag { color:var(--av-muted); }
 
   /* Re-theme the live HUD chrome when Age View is on. CSS-var driven, so the showroom's
      own component CSS doesn't change — only the values do. Banded snap, not interpolation. */
diff --git a/scripts/verify-ageview-phase5.mjs b/scripts/verify-ageview-phase5.mjs
index c13b162..4ba805c 100644
--- a/scripts/verify-ageview-phase5.mjs
+++ b/scripts/verify-ageview-phase5.mjs
@@ -149,6 +149,12 @@ const FPS_FLOOR   = 55;
   console.log('Age View ON =', isOn);
 
   // ---------- 5.1 FPS gate across every band ----------
+  // Warm-up apply: the FIRST band measured right after setOn(true) otherwise eats
+  // the one-time Age-View activation cost (font load, style inject, first env morph),
+  // which is NOT steady-state render. Burn one throwaway band + settle before the
+  // measured loop so every band is sampled at steady state. Floor unchanged at 55.
+  await page.evaluate(() => window._ageview.apply(28));
+  await sleep(1600);
   console.log('\n=== 5.1 FPS gate (≥' + FPS_FLOOR + ' at every band) ===');
   for (const age of ALL_AGES) {
     await page.evaluate(a => window._ageview.apply(a), age);
@@ -158,6 +164,37 @@ const FPS_FLOOR   = 55;
     console.log(`  age ${String(age).padStart(2)}  min=${fps.min}  avg=${fps.avg}  (${report.fps[age].pass ? 'PASS' : 'FAIL'})`);
   }
 
+  // ---------- 5.1b Age View BAR legibility at EVERY band ----------
+  // The persistent Age View slider bar (age#, band name, tag) must stay readable at
+  // every band — not just the senior bands. Several bands carry dark text colours
+  // tuned for their OWN light panel that vanish on the bar's dark glass (tween,
+  // connoisseur, tastemaker had age#/band Lc≈0, found + fixed 2026-06-29). Measured
+  // APCA on the rendered text/bar hex pair; floor 45 for the age# (display weight,
+  // 28px+) and band/tag (the readout copy). Senior bands clear this trivially.
+  console.log('\n=== 5.1b Age View BAR legibility (every band, measured APCA) ===');
+  const BAR_FLOOR = 45;
+  report.barLegibility = {};
+  let barAllPass = true;
+  for (const age of ALL_AGES) {
+    await page.evaluate(a => window._ageview.apply(a), age);
+    await sleep(500);
+    const r = await page.evaluate(() => {
+      const lc = (txt, bg) => Math.abs(window._ageview.apcaLc(txt, bg));
+      const meas = (id) => {
+        const el = document.getElementById(id);
+        if (!el || !window.__visible(el)) return null;
+        const bg = window.__effectiveBg(el), fg = window.__textHex(el);
+        return { lc: (bg.opaque && fg) ? lc(fg, bg.hex) : null, fg, bg: bg.hex, px: Math.round(window.__fontPx(el)) };
+      };
+      return { band: document.body.dataset.ageBand, age: meas('av-age'), bandName: meas('av-band'), tag: meas('av-tag') };
+    });
+    const aLc = r.age?.lc ?? 0, bLc = r.bandName?.lc ?? 0, tLc = r.tag?.lc ?? 0;
+    const pass = aLc >= BAR_FLOOR && bLc >= BAR_FLOOR && tLc >= BAR_FLOOR;
+    if (!pass) barAllPass = false;
+    report.barLegibility[age] = { ...r, pass };
+    console.log(`  age ${String(age).padStart(2)} [${r.band}]  age#Lc=${aLc}  bandLc=${bLc}  tagLc=${tLc}  (${pass ? 'PASS' : 'FAIL'})`);
+  }
+
   // ---------- 5.2 Senior measured gate ----------
   console.log('\n=== 5.2 Senior-usability MEASURED gate (65/75/85) ===');
   // Roles: which visible elements carry body text, headings, CTAs, interactive targets.
@@ -302,13 +339,14 @@ const FPS_FLOOR   = 55;
   const regPass = report.regression.pass;
   const errPass = errors.length === 0;
   report.verdict = {
-    fpsPass, seniorPass, regPass, errPass, consoleErrors: errors.length,
-    overall: fpsPass && seniorPass && regPass && errPass,
+    fpsPass, barPass: barAllPass, seniorPass, regPass, errPass, consoleErrors: errors.length,
+    overall: fpsPass && barAllPass && seniorPass && regPass && errPass,
   };
 
   fs.writeFileSync(path.join(OUT, 'phase5-result.json'), JSON.stringify(report, null, 2));
   console.log('\n================ PHASE 5 VERDICT ================');
   console.log('  FPS gate     :', fpsPass ? 'PASS' : 'FAIL');
+  console.log('  Bar legib.   :', barAllPass ? 'PASS' : 'FAIL');
   console.log('  Senior gate  :', seniorPass ? 'PASS' : 'FAIL');
   console.log('  Regression   :', regPass ? 'PASS' : 'FAIL');
   console.log('  Console errs :', errors.length, errPass ? 'PASS' : 'FAIL');

← 796c25f walls-on-next: add Age-View-ON regression pass (paging under  ·  back to Quadrille Showroom  ·  Loop ledger iter5: two clean clickthrough sweeps + visual co 66c765f →