[object Object]

← back to Rentv Sheet Enrich Refine

rentv :9797 legend: Cody fixes — sticky header (✕ stays visible on scroll) + Escape cancels a rename

72a7312800a2bb9e553a04185f5ade8a093f287a · 2026-08-13 20:15:36 -0700 · Steve Abrams

- .lghead now position:sticky + ✕ floats within it, so the close button no longer scrolls
  out of view on tabs with many colors
- Escape sets a _legEsc flag before closeLegend so the blur handler does NOT commit a
  half-typed rename (Escape = cancel, not save)

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

Files touched

Diff

commit 72a7312800a2bb9e553a04185f5ade8a093f287a
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Aug 13 20:15:36 2026 -0700

    rentv :9797 legend: Cody fixes — sticky header (✕ stays visible on scroll) + Escape cancels a rename
    
    - .lghead now position:sticky + ✕ floats within it, so the close button no longer scrolls
      out of view on tabs with many colors
    - Escape sets a _legEsc flag before closeLegend so the blur handler does NOT commit a
      half-typed rename (Escape = cancel, not save)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 public_live/index.html | 31 ++++++++++++++++++++++---------
 1 file changed, 22 insertions(+), 9 deletions(-)

diff --git a/public_live/index.html b/public_live/index.html
index d4bcce7..47f4166 100644
--- a/public_live/index.html
+++ b/public_live/index.html
@@ -91,7 +91,7 @@
   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 .lghead{font-weight:700;color:var(--dim);padding:2px 4px 8px;border-bottom:1px solid var(--line);margin-bottom:6px;position:sticky;top:-8px;background:var(--panel);z-index:2}
   .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}
@@ -101,7 +101,7 @@
   .legendpanel .lgname:focus{box-shadow:inset 0 0 0 2px var(--accent)}
   .legendpanel .lgname.lgguess{opacity:.72;font-style:italic}
   .legendpanel .lgq{color:var(--dim);font-weight:700;font-size:11px;flex:0 0 auto}
-  .legendpanel .lgclose{position:absolute;top:6px;right:8px;background:transparent;border:0;color:var(--dim);cursor:pointer;font-size:14px;line-height:1;padding:2px 4px;z-index:1}
+  .legendpanel .lgclose{float:right;background:transparent;border:0;color:var(--dim);cursor:pointer;font-size:14px;line-height:1;padding:0 2px;margin:-1px -2px 0 8px}
   .legendpanel .lgclose:hover{color:var(--ink)}
   .legendpanel .lgreset{background:transparent;border:0;color:var(--dim);cursor:pointer;font-size:12px;padding:0 3px;flex:0 0 auto}
   .legendpanel .lgreset:hover{color:var(--accent)}
@@ -128,6 +128,9 @@
   /* color-coded Status field + click-to-set picker */
   td.statuscell{cursor:pointer} td.statuscell:hover{background:var(--rowhover)}
   .statuschip{display:inline-block;max-width:100%;padding:2px 10px;border-radius:12px;font-size:.92em;font-weight:600;line-height:1.55;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+  /* derived 🔥 Warmth column — read-only heat pill */
+  td.warmthcell{text-align:center;cursor:default}
+  .heatpill{display:inline-block;min-width:34px;padding:1px 9px;border-radius:11px;font-weight:700;font-variant-numeric:tabular-nums}
   td.statuscell.empty{color:var(--dim);font-size:.9em}
   td.statuscell .chipdate{display:block;margin-top:3px;color:var(--dim);font-size:.72em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-variant-numeric:tabular-nums}
   .statusmenu{position:fixed;z-index:70;background:var(--panel);border:1px solid var(--line);border-radius:10px;box-shadow:0 12px 34px #000a;padding:5px;min-width:230px;max-height:70vh;overflow:auto}
@@ -293,10 +296,12 @@ async function loadData(){
       o.splice(o.indexOf(nc)+1,0,fn,ln); ORDER=o;
     }
   }
-  // restore saved sort for THIS view; else default to Last Name ascending
+  // restore saved sort for THIS view; else default to 🔥 Warmth desc (work-first), then Last Name asc
   const savedSort=LS.get('sort.'+K, undefined);
   if(savedSort!==undefined){ SORT=savedSort; }
-  else { const lnIdx=DATA.headers.findIndex(h=>/^last name$/i.test(String(h).trim())); SORT = lnIdx>=0 ? {col:lnIdx, dir:1} : null; }
+  else { const wIdx=DATA.headers.findIndex(h=>String(h).trim()==='🔥 Warmth');
+    if(wIdx>=0){ SORT={col:wIdx, dir:-1}; }
+    else { const lnIdx=DATA.headers.findIndex(h=>/^last name$/i.test(String(h).trim())); SORT = lnIdx>=0 ? {col:lnIdx, dir:1} : null; } }
   // restore saved density
   const dens=LS.get('dens',null); if(dens) document.documentElement.style.setProperty('--fs',dens+'px');
   buildRail(); render(); setSync('ok');
@@ -316,8 +321,10 @@ function passes(r){
   return true;
 }
 function filtered(){ let rows=DATA.rows.map((r,i)=>[r,i]).filter(([r])=>passes(r));
-  if(SORT){ const ci=SORT.col,dir=SORT.dir; rows.sort((a,b)=>{const x=g(a[0],ci).toLowerCase(),y=g(b[0],ci).toLowerCase();
+  if(SORT){ const ci=SORT.col,dir=SORT.dir; rows.sort((a,b)=>{const rx=g(a[0],ci),ry=g(b[0],ci),x=rx.toLowerCase(),y=ry.toLowerCase();
     if(!x&&!y)return 0; if(!x)return 1; if(!y)return -1;   // blanks always sink to the bottom
+    const nx=Number(rx),ny=Number(ry);                     // numeric-aware: 🔥 Warmth, # Appearances sort as numbers not text
+    if(rx!==''&&ry!==''&&!isNaN(nx)&&!isNaN(ny))return (nx-ny)*dir;
     return (x<y?-1:x>y?1:0)*dir}); }
   return rows;
 }
@@ -571,12 +578,12 @@ function renderLegend(){ let p=$('#legendpanel'); if(p&&document.activeElement&&
   if(!p){p=document.createElement('div');p.id='legendpanel';p.className='legendpanel';document.body.appendChild(p);}
   const items=colorTally();
   const CLOSE='<button class="lgclose" title="Close (Esc)">✕</button>';
-  if(!items.length){ p.innerHTML=CLOSE+'<div class="lghead">Color key — this tab</div><div class="lgempty">No color-coded cells on this tab.</div>'; p.querySelector('.lgclose').onclick=closeLegend; return; }
-  p.innerHTML=CLOSE+'<div class="lghead">Color key — this tab<span class="lgnote">Exact sheet colors + provisional meaning. Click a name to rename (saved locally); ↺ resets a rename. ❓ / grey “?” = unverified guess.</span></div>'
+  if(!items.length){ p.innerHTML='<div class="lghead">'+CLOSE+'Color key — this tab</div><div class="lgempty">No color-coded cells on this tab.</div>'; p.querySelector('.lgclose').onclick=closeLegend; return; }
+  p.innerHTML='<div class="lghead">'+CLOSE+'Color key — this tab<span class="lgnote">Exact sheet colors + provisional meaning. Click a name to rename (saved locally); ↺ resets a rename. ❓ / grey “?” = unverified guess.</span></div>'
     +items.map(([hx,n])=>{const sure=nameSure(hx);const ov=LS.get('cname.'+hx,null),hasOv=ov!=null&&ov!=='';const mid=hasOv?`<button class="lgreset" data-hx="${hx}" title="Reset to provisional name">↺</button>`:(sure?'':'<span class="lgq" title="unverified guess">?</span>');return `<div class="lgrow"><span class="lgsw" style="background:${hx}"></span><span class="lgname${sure?'':' lgguess'}" contenteditable="true" data-hx="${hx}" title="${sure?'Click to rename':'Heuristic guess — click to rename'}">${esc(provName(hx))}</span>${mid}<span class="lghex">${hx}</span><span class="lgn">${n.toLocaleString()}</span></div>`;}).join('');
   p.querySelector('.lgclose').onclick=closeLegend;
   p.querySelectorAll('.lgreset').forEach(b=>b.onclick=()=>{ localStorage.removeItem('rc2.cname.'+b.dataset.hx); renderLegend(); });
-  p.querySelectorAll('.lgname').forEach(el=>{ el.onblur=()=>{ const v=el.textContent.trim(); if(v)LS.set('cname.'+el.dataset.hx, v); };
+  p.querySelectorAll('.lgname').forEach(el=>{ el.onblur=()=>{ if(window._legEsc){window._legEsc=false;return;} const v=el.textContent.trim(); if(v)LS.set('cname.'+el.dataset.hx, v); };
     el.onkeydown=e=>{ if(e.key==='Enter'){e.preventDefault();el.blur();} }; });
 }
 function closeLegend(){ const p=$('#legendpanel'); if(p)p.style.display='none'; document.removeEventListener('mousedown',legendOutside); }
@@ -612,9 +619,15 @@ function openStatusPicker(td){
 }
 function closeStatusPicker(){const m=$('#statusmenu');if(m)m.remove();document.removeEventListener('mousedown',closeStatusPickerOutside);}
 function closeStatusPickerOutside(e){ if(!e.target.closest('#statusmenu')&&!e.target.closest('.statuscell'))closeStatusPicker(); }
+function isWarmthCol(ci){ return DATA.headers && String(DATA.headers[ci]).trim()==='🔥 Warmth'; }
 function cellTD(ri,ci,val,isN,gr){
   const cls=[isN?'namecell':'',gr?'enriched':''].filter(Boolean).join(' ');
   const s=String(val==null?'':val);
+  if(isWarmthCol(ci)){                                       // derived, read-only heat pill (hotter = deeper warm tone)
+    const n=Number(s)||0, t=Math.max(0,Math.min(1,n/120));
+    const bg=`hsl(${Math.round(16-t*6)} 90% ${Math.round(90-52*t)}%)`, fg=t>0.55?'#fff':'#5a2600';
+    return `<td data-r="${ri}" data-c="${ci}" contenteditable="false" class="warmthcell ${cls}" title="Warmth ${esc(s)} — derived (appearances + status + reachability), read-only"><span class="heatpill" style="background:${bg};color:${fg}">${esc(s)}</span></td>`;
+  }
   const xc=COLORS[ri+','+ci], xt=xc?txtOn(xc):'';   // EXACT original sheet cell color + auto-contrast text
   const xsty=xc?` style="background:${xc};color:${xt};--xtext:${xt}"`:'', xcls=xc?' xcol':'';
   if(isStatusCol(ci)){                                          // color-coded status chip + date/time under it + click-to-set picker
@@ -812,7 +825,7 @@ applyTheme(LS.get('theme', (window.matchMedia&&matchMedia('(prefers-color-scheme
 // Fit mode = table always fits the window: re-fit columns to the viewport when it resizes
 window.addEventListener('resize',()=>{ if(WIDTHPRESET==='fit'){ clearTimeout(window._rzfit); window._rzfit=setTimeout(applyFit,120); } });
 document.addEventListener('dragend',()=>{ window._dragText=false; });   // clear the text-move guard after any drag
-document.addEventListener('keydown',e=>{if(e.key==='Escape'){closeDraft();closeLegend();}});
+document.addEventListener('keydown',e=>{if(e.key==='Escape'){closeDraft(); if($('#legendpanel')&&$('#legendpanel').style.display==='block'){window._legEsc=true;closeLegend();}}});
 (async()=>{await loadMeta();await loadData(); if(WIDTHPRESET==='fit')applyFit();})();
 </script>
 </body>

← 21e6af5 rentv :9797 legend: close (✕/Esc/click-outside) + per-row ↺  ·  back to Rentv Sheet Enrich Refine  ·  Add derived 🔥 Warmth score column to RENTV live console + t ddffc49 →