β 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 β