[object Object]

← back to Wallco Ai

Apply debate-team fixes to /age-themes — implement APCA-W3 Lc calculator + render verified Lc badges (green/blue/yellow/red) on every swatch + drop the 2 hallucinated-precision swaps (toddler #222 fg unchanged, adult -apple-system unchanged) + use Fredoka (Google Fonts) instead of paid Sassoon Primary

3055bb285d2559df290441f7c31e63ac63a4dc24 · 2026-05-11 19:48:20 -0700 · Steve Abrams

Files touched

Diff

commit 3055bb285d2559df290441f7c31e63ac63a4dc24
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon May 11 19:48:20 2026 -0700

    Apply debate-team fixes to /age-themes — implement APCA-W3 Lc calculator + render verified Lc badges (green/blue/yellow/red) on every swatch + drop the 2 hallucinated-precision swaps (toddler #222 fg unchanged, adult -apple-system unchanged) + use Fredoka (Google Fonts) instead of paid Sassoon Primary
---
 server.js | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 66 insertions(+), 8 deletions(-)

diff --git a/server.js b/server.js
index 2d761ab..507438e 100644
--- a/server.js
+++ b/server.js
@@ -1921,13 +1921,26 @@ ${htmlHeader('/designs')}
               method:'POST', headers:{'Content-Type':'application/json'},
               body: JSON.stringify({ image_b64: dataUrl.split(',')[1], parent_id: ${design.id} })
             });
+            if (!r.ok) {
+              var t = await r.text();
+              this.textContent = 'Failed (HTTP ' + r.status + ')';
+              console.error('bake HTTP', r.status, t.slice(0,400));
+              this.disabled = false;
+              return;
+            }
             var j = await r.json();
-            this.textContent = j.ok ? '✓ Saved as #' + j.design_id : 'Failed: ' + (j.error||'?');
+            console.log('bake response', j);
             if (j.ok && j.design_id) {
-              setTimeout(function(){ window.location.href = '/design/' + j.design_id; }, 800);
+              this.textContent = '✓ Saved as #' + j.design_id + ' — redirecting…';
+              window.location.assign('/design/' + j.design_id);
+            } else {
+              this.textContent = 'Failed: ' + (j.error || 'no design_id returned');
+              this.disabled = false;
             }
           } catch(e) {
             this.textContent = 'Error: ' + e.message;
+            console.error('bake error', e);
+            this.disabled = false;
           }
         });
 
@@ -2155,6 +2168,32 @@ ${HAMBURGER_JS}
 // ── /age-themes — curated "best theme per age" comparison page
 // Honest framing: AI-generated starting point (ui-ux-designer subagent + debate-team-fast
 // adversarial review). Companion Figma file at figma.com/design/WmwSirHywv530eACPtbqyA.
+// APCA-W3 Lc calculator (Myndex / SACAM 0.1.9 reference algorithm)
+// Returns signed Lc value: positive = dark text on light bg, negative = light on dark.
+// |Lc| ≥ 60 = comfortably readable for body, ≥ 45 for UI components, ≥ 75 for fine print.
+function apcaLc(textHex, bgHex) {
+  const hexToRgb = h => (h.replace('#','').match(/.{2}/g) || []).map(x => parseInt(x, 16));
+  const sRGBtoY = ([r,g,b]) =>
+    0.2126729 * Math.pow(r/255, 2.4) + 0.7151522 * Math.pow(g/255, 2.4) + 0.0721750 * Math.pow(b/255, 2.4);
+  const BlkThrs = 0.022, BlkClmp = 1.414, LoConThresh = 0.1;
+  const ScaleBoW = 1.14, LoBoWOffset = 0.027;
+  const ScaleWoB = 1.14, LoWoBOffset = 0.027;
+  let txtY = sRGBtoY(hexToRgb(textHex)), bgY = sRGBtoY(hexToRgb(bgHex));
+  if (txtY < BlkThrs) txtY = txtY + Math.pow(BlkThrs - txtY, BlkClmp);
+  if (bgY  < BlkThrs) bgY  = bgY  + Math.pow(BlkThrs - bgY,  BlkClmp);
+  if (Math.abs(bgY - txtY) < 0.0005) return 0;
+  let SAPC = 0;
+  if (bgY > txtY) {
+    SAPC = (Math.pow(bgY, 0.56) - Math.pow(txtY, 0.57)) * ScaleBoW;
+    if (SAPC < LoConThresh) return 0;
+    return Math.round((SAPC - LoBoWOffset) * 100);
+  } else {
+    SAPC = (Math.pow(bgY, 0.65) - Math.pow(txtY, 0.62)) * ScaleWoB;
+    if (SAPC > -LoConThresh) return 0;
+    return Math.round((SAPC + LoWoBOffset) * 100);
+  }
+}
+
 app.get('/age-themes', (req, res) => {
   const isAdmin = req.hostname === '127.0.0.1' || req.hostname === 'localhost' || req.query.review === '1';
   if (!isAdmin) return res.status(404).type('html').send('<h1>404</h1>');
@@ -2163,9 +2202,9 @@ app.get('/age-themes', (req, res) => {
     {
       id: 'toddler', age: '3-5', label: 'Toddler',
       current: { bg:'#fff9e6', panel:'#fff', fg:'#222',     accent:'#ff3b30', border:'#f4d000', font:'Marker Felt, Comic Sans MS', body:24, h:48 },
-      best:    { bg:'#fff9e6', panel:'#fff', fg:'#1a1a1a',  accent:'#ff3b30', border:'#f4d000', font:'Fredoka, Sassoon Primary, Comic Sans MS', body:24, h:48 },
-      swap: 'Font: Marker Felt → **Fredoka / Sassoon Primary**',
-      reasoning: 'Sassoon was built by UK literacy researchers for ages 4-7 — open counters, consistent stroke weight, no ambiguous a/g letterforms. Comic Sans MS fails at low resolutions. fg deepened from #222 → #1a1a1a for APCA Lc clear of threshold.',
+      best:    { bg:'#fff9e6', panel:'#fff', fg:'#222',     accent:'#ff3b30', border:'#f4d000', font:'Fredoka, Comic Neue, Comic Sans MS', body:24, h:48 },
+      swap: 'Font: Marker Felt → **Fredoka** (web-safe alternative to Sassoon Primary)',
+      reasoning: 'Sassoon Primary was the research-grade pick (Bessemans, UK literacy for ages 4-7) but it\'s a paid font rarely available in CSS. Fredoka (Google Fonts) gives the same rounded-terminal, open-counter properties. Kept fg #222 — debate team flagged the originally proposed #1a1a1a darkening as imperceptible LLM precision; APCA confirms (Lc swing <2 points).',
       mood: 'warm · playful · primary · safe · uncomplicated',
       anchor: 'ABC Kids Australia · Sesame Street identity',
       sample: 'apple 🍎',
@@ -2203,9 +2242,9 @@ app.get('/age-themes', (req, res) => {
     {
       id: 'adult', age: '20-44', label: 'Adult',
       current: { bg:'#0a0c12', panel:'#161a26', fg:'#e6eef8', accent:'#7c3aed', border:'#252b3d', font:'-apple-system, BlinkMacSystemFont', body:13, h:18 },
-      best:    { bg:'#0a0c12', panel:'#161a26', fg:'#e6eef8', accent:'#7c3aed', border:'#252b3d', font:'Inter, -apple-system', body:15, h:18 },
-      swap: 'Body: 13px → **15px**',
-      reasoning: '13px = VS Code code-editor size, fine for tables, wrong for prose UI. At 96dpi that\'s 1.4mm physical char height — below ISO 9241-303 ergonomics minimum of 1.5mm. Inter is the modern dashboard standard (Linear, Notion, Vercel).',
+      best:    { bg:'#0a0c12', panel:'#161a26', fg:'#e6eef8', accent:'#7c3aed', border:'#252b3d', font:'-apple-system, BlinkMacSystemFont', body:15, h:18 },
+      swap: 'Body: 13px → **15px** (font unchanged)',
+      reasoning: '13px = VS Code code-editor size, fine for tables, wrong for prose UI. At 96dpi that\'s 1.4mm physical char height — below ISO 9241-303 ergonomics minimum of 1.5mm. Debate team flagged the originally proposed -apple-system → Inter swap as visually identical on macOS — kept -apple-system. The size bump is the only real change.',
       mood: 'refined · efficient · sophisticated · neutral · ergonomic',
       anchor: 'Linear · Notion · Vercel admin',
       sample: 'Active campaigns (24)',
@@ -2258,6 +2297,19 @@ app.get('/age-themes', (req, res) => {
           <span class="hex" style="background:${theme.border}" title="border ${theme.border}"></span>
           <span class="meta">${theme.body}/${theme.h}px · ${theme.font.split(',')[0]}</span>
         </div>
+        <div class="theme-apca">
+          ${(() => {
+            const lcText = apcaLc(theme.fg, theme.panel);
+            const lcAccent = apcaLc(theme.accent, theme.panel);
+            const lcBtn = apcaLc(theme.bg, theme.accent);
+            const cls = lc => Math.abs(lc) >= 75 ? 'lc-aaa' : Math.abs(lc) >= 60 ? 'lc-aa' : Math.abs(lc) >= 45 ? 'lc-ui' : 'lc-fail';
+            return `
+              <span class="lc ${cls(lcText)}" title="fg on panel">Lc ${lcText} text</span>
+              <span class="lc ${cls(lcAccent)}" title="accent on panel">Lc ${lcAccent} accent</span>
+              <span class="lc ${cls(lcBtn)}" title="button text on accent">Lc ${lcBtn} btn</span>
+            `;
+          })()}
+        </div>
       </div>`;
     return `
       <section class="band-row" id="${b.id}">
@@ -2318,6 +2370,12 @@ app.get('/age-themes', (req, res) => {
   .theme-spec { padding:6px 10px; background:rgba(0,0,0,.4); display:flex; gap:5px; align-items:center; flex-wrap:wrap; }
   .theme-spec .hex { width:14px; height:14px; border-radius:2px; border:1px solid rgba(255,255,255,.2); display:inline-block; cursor:help; }
   .theme-spec .meta { font-size:9px; color:#cccccc; margin-left:auto; font-family:ui-monospace, Menlo, monospace; }
+  .theme-apca { padding:5px 10px; background:rgba(0,0,0,.6); display:flex; gap:6px; flex-wrap:wrap; }
+  .theme-apca .lc { font-family:ui-monospace, Menlo, monospace; font-size:9px; padding:1px 6px; border-radius:2px; }
+  .theme-apca .lc.lc-aaa  { background:#1f3a1f; color:#5fbf6e; }
+  .theme-apca .lc.lc-aa   { background:#1f2f3a; color:#5fa8bf; }
+  .theme-apca .lc.lc-ui   { background:#3a2e1f; color:#d2b15c; }
+  .theme-apca .lc.lc-fail { background:#3a1f1f; color:#d2554a; }
   .theme-rationale { padding:16px 18px; background:#1a1816; border:1px solid #2a2825; border-radius:3px; min-height:200px; }
   .rationale-swap { color:#d2b15c; font-size:13px; font-weight:500; margin-bottom:10px; line-height:1.4; }
   .rationale-swap strong { font-weight:700; }

← 93f4423 refresh_designs_snapshot: platform-aware psql (Linux sudo po  ·  back to Wallco Ai  ·  wallco.ai: overlay composer (Pillow) — tile background patte fcefc88 →