[object Object]

← back to Rentv Sheet Enrich Refine

rentv :9797: add in-viewer Color Key legend panel (🎨 Colors) β€” swatch + count + editable provisional name

aba95a9a8cfb3602c5c5e69b657d76e75fb5bc66 Β· 2026-08-13 18:39:26 -0700 Β· Steve Abrams

Reads the client COLORS map for the loaded tab, tallies distinct exact colors, shows
each with a swatch, hex, cell-count and a grounded provisional meaning (blue=Contacted,
green=Has email, magenta=Needs email, cyan/yellow flagged ❓ awaiting Steve). Names are
click-to-rename, persisted to localStorage β€” reversible, NO sheet write. Addresses Cody's
'color chaos with no key' note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit aba95a9a8cfb3602c5c5e69b657d76e75fb5bc66
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Aug 13 18:39:26 2026 -0700

    rentv :9797: add in-viewer Color Key legend panel (🎨 Colors) β€” swatch + count + editable provisional name
    
    Reads the client COLORS map for the loaded tab, tallies distinct exact colors, shows
    each with a swatch, hex, cell-count and a grounded provisional meaning (blue=Contacted,
    green=Has email, magenta=Needs email, cyan/yellow flagged ❓ awaiting Steve). Names are
    click-to-rename, persisted to localStorage β€” reversible, NO sheet write. Addresses Cody's
    'color chaos with no key' note.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 public_live/index.html | 42 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/public_live/index.html b/public_live/index.html
index e8f8e34..0b58a55 100644
--- a/public_live/index.html
+++ b/public_live/index.html
@@ -89,6 +89,18 @@
     white-space:pre-wrap;word-break:break-word;overflow:visible;text-overflow:clip;min-width:260px;max-width:520px;position:relative;z-index:4}
   td.saved{background:var(--green)!important;color:var(--greenink)!important}
   td.xcol .linkval,td.xcol .cicon{color:var(--xtext)!important}   /* exact-color cell: keep link/icon text readable on the painted bg */
+  /* color key / legend panel (exact sheet colors -> provisional meanings) */
+  .legendpanel{position:fixed;right:14px;top:52px;z-index:80;background:var(--panel);border:1px solid var(--line);border-radius:10px;box-shadow:0 12px 34px #000a;padding:8px;min-width:290px;max-width:380px;max-height:74vh;overflow:auto;display:none;font-size:12px}
+  .legendpanel .lghead{font-weight:700;color:var(--dim);padding:2px 4px 8px;border-bottom:1px solid var(--line);margin-bottom:6px}
+  .legendpanel .lgnote{font-weight:400;font-size:10px;display:block;margin-top:2px;color:var(--dim)}
+  .legendpanel .lgempty{color:var(--dim);padding:8px 4px}
+  .legendpanel .lgrow{display:flex;align-items:center;gap:8px;padding:3px 4px;border-radius:6px}
+  .legendpanel .lgrow:hover{background:var(--rowhover)}
+  .legendpanel .lgsw{width:16px;height:16px;border-radius:4px;border:1px solid rgba(128,128,128,.4);flex:0 0 auto}
+  .legendpanel .lgname{flex:1;outline:none;border-radius:4px;padding:1px 4px;min-width:80px}
+  .legendpanel .lgname:focus{box-shadow:inset 0 0 0 2px var(--accent)}
+  .legendpanel .lghex{color:var(--dim);font-variant-numeric:tabular-nums;font-size:10.5px}
+  .legendpanel .lgn{color:var(--dim);font-variant-numeric:tabular-nums;min-width:46px;text-align:right}
   td.enriched{box-shadow:inset 3px 0 0 #7bc043;background:rgba(123,192,67,.09)}
   td a{color:var(--accent);text-decoration:none} .namecell{font-weight:600}
   a.iconbtn,a.callbtn{display:inline-flex;align-items:center;justify-content:center;margin-left:5px;text-decoration:none;cursor:pointer;opacity:.72;vertical-align:middle}
@@ -177,6 +189,7 @@
   <span class="grow"></span>
   <span class="sync"><span class="dot" id="dot"></span><span id="syncmsg">loading…</span></span>
   <button id="theme" title="Night / Day">πŸŒ™</button>
+  <button id="legendtog" title="Color key β€” what the exact sheet colors mean on this tab">🎨 Colors</button>
   <button id="refresh" title="Rebuild local copy from the sheet">↻ Refresh</button>
 </header>
 
@@ -260,6 +273,7 @@ async function loadData(){
   else { d=await (await fetch('/api/combined?gids='+gids.join(','))).json(); }
   LS.set('sheets',[...SELECTED]);
   DATA=d; GREENSET=new Set(d.green||[]); COLORS=d.colors||{}; detectRoles();
+  if($('#legendpanel')&&$('#legendpanel').style.display==='block')renderLegend();   // refresh color key per tab
   FILTERS={}; SEARCH=''; $('#search')&&($('#search').value='');
   const K=curKey();
   HIDDEN=new Set(LS.get('hidden.'+K,[]));
@@ -529,6 +543,33 @@ function hashColor(s){ let h=0; for(let i=0;i<s.length;i++)h=(h*31+s.charCodeAt(
 function statusColor(v){ return (STATUS_COLORS&&STATUS_COLORS[v]) || STATUS_DEFAULTS[v] || hashColor(v); }
 function txtOn(color){ const m=color.match(/^#([0-9a-f]{6})$/i); if(!m)return '#111';   // hsl fallbacks are light -> dark text
   const n=parseInt(m[1],16), r=n>>16, g=(n>>8)&255, b=n&255; return (0.299*r+0.587*g+0.114*b)>150?'#111':'#fff'; }
+// ---- color key / legend (exact sheet colors -> provisional, editable meanings; no sheet write) ----
+const CNAMED={'#cfe2f3':'Contacted','#0000ff':'Contacted','#3d85c6':'Contacted','#4a86e8':'Contacted','#00ffff':'Contacted ❓','#00ff00':'Has email','#ff00ff':'Needs email / Outreach pending','#ffff00':'Prospect / Follow-up ❓'};
+function provName(hex){ const o=LS.get('cname.'+hex,null); if(o!=null)return o;   // user rename wins (saved locally)
+  if(CNAMED[hex])return CNAMED[hex];
+  const m=hex.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i); if(!m)return '';
+  const r=parseInt(m[1],16),g=parseInt(m[2],16),b=parseInt(m[3],16),mx=Math.max(r,g,b),mn=Math.min(r,g,b),d=mx-mn;
+  if(d<22)return 'Section / On hold';                              // greyscale
+  let h; if(mx===r)h=((g-b)/d)%6; else if(mx===g)h=(b-r)/d+2; else h=(r-g)/d+4; h=(h*60+360)%360;
+  if(h<20||h>=330)return 'Flag / Priority';                        // red
+  if(h<45)return 'In progress / Left message';                     // orange
+  if(h<70)return 'Prospect / Follow-up';                           // yellow
+  if(h<160)return 'Has email';                                     // green
+  if(h<200)return 'Contacted';                                     // cyan/aqua
+  if(h<290)return 'Contacted';                                     // blue
+  return 'Needs email / Outreach pending';                         // magenta/pink
+}
+function colorTally(){ const t={}; for(const hx of Object.values(COLORS))t[hx]=(t[hx]||0)+1; return Object.entries(t).sort((a,b)=>b[1]-a[1]); }
+function renderLegend(){ let p=$('#legendpanel'); if(!p){p=document.createElement('div');p.id='legendpanel';p.className='legendpanel';document.body.appendChild(p);}
+  const items=colorTally();
+  if(!items.length){ p.innerHTML='<div class="lghead">Color key β€” this tab</div><div class="lgempty">No color-coded cells on this tab.</div>'; return; }
+  p.innerHTML='<div class="lghead">Color key β€” this tab<span class="lgnote">Exact sheet colors + provisional meaning. Click a name to rename (saved locally). ❓ = awaiting Steve.</span></div>'
+    +items.map(([hx,n])=>`<div class="lgrow"><span class="lgsw" style="background:${hx}"></span><span class="lgname" contenteditable="true" data-hx="${hx}" title="Click to rename">${esc(provName(hx))}</span><span class="lghex">${hx}</span><span class="lgn">${n.toLocaleString()}</span></div>`).join('');
+  p.querySelectorAll('.lgname').forEach(el=>{ el.onblur=()=>LS.set('cname.'+el.dataset.hx, el.textContent.trim());
+    el.onkeydown=e=>{ if(e.key==='Enter'){e.preventDefault();el.blur();} }; });
+}
+function toggleLegend(){ let p=$('#legendpanel'); const open=p&&p.style.display==='block';
+  if(open){ p.style.display='none'; return; } renderLegend(); $('#legendpanel').style.display='block'; }
 function isStatusCol(ci){ return STATUS_FIELD && DATA.headers[ci]===STATUS_FIELD; }
 function lmCol(){ return DATA.headers.findIndex(h=>/^last modified$/i.test(String(h).trim())); }
 async function stampLastMod(r){   // write "now" to the row's Last Modified cell (shown under the status chip)
@@ -739,6 +780,7 @@ $('#tab').onchange=e=>{SELECTED=new Set([+e.target.value]);loadData()};
 $$('#viewseg button').forEach(b=>b.onclick=()=>{VIEW=b.dataset.v;LS.set('view',VIEW);render()});
 $('#railtog').onclick=()=>{$('#rail').classList.toggle('hide');if(!$('#rail').classList.contains('hide'))buildRail()};
 $('#refresh').onclick=async()=>{setSync('load');$('#refresh').textContent='↻ refreshing…';try{await fetch('/api/refresh',{method:'POST',headers:{'Content-Type':'application/json'},body:'{}'});await loadMeta();$('#tab').value=GID;await loadData()}catch(e){setSync('err')}$('#refresh').textContent='↻ Refresh'};
+$('#legendtog').onclick=toggleLegend;
 $('#dclose').onclick=closeDraft;$('#scrim').onclick=closeDraft;
 (function(){const dr=$('#drawer'),h=$('#dresize');const sw=LS.get('drawerW',0);if(sw)dr.style.width=sw+'px';
   h.addEventListener('mousedown',e=>{e.preventDefault();dr.classList.add('resizing');

← 60e8dcd auto-data-snapshot: 2026-08-13T18:37:41 (1 data files) β€” __p  Β·  back to Rentv Sheet Enrich Refine  Β·  rentv :9797 legend: Cody fixes β€” empty-rename guard, mark he 46c2c4d β†’