← back to Commercialrealestate
chore: untrack crcp-grid .pre-3view.bak backups (TK-10526; kept on disk, *.bak gitignored)
ded628b24da054f5ecda622a6ccf18cf548589d8 · 2026-08-13 09:50:58 -0700 · Steve Abrams
Files touched
D public/crcp-grid.css.pre-3view.bakD public/crcp-grid.js.pre-3view.bak
Diff
commit ded628b24da054f5ecda622a6ccf18cf548589d8
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Aug 13 09:50:58 2026 -0700
chore: untrack crcp-grid .pre-3view.bak backups (TK-10526; kept on disk, *.bak gitignored)
---
public/crcp-grid.css.pre-3view.bak | 56 -----------
public/crcp-grid.js.pre-3view.bak | 191 -------------------------------------
2 files changed, 247 deletions(-)
diff --git a/public/crcp-grid.css.pre-3view.bak b/public/crcp-grid.css.pre-3view.bak
deleted file mode 100644
index 9642cd2..0000000
--- a/public/crcp-grid.css.pre-3view.bak
+++ /dev/null
@@ -1,56 +0,0 @@
-/* crcp-grid.css — styles for the reusable CrcpGrid engine. Namespaced .cg-* so it never
- * collides with a host page. Colors read the host's CSS vars with safe fallbacks, so the
- * engine inherits each page's theme (condos dark, fha-loans glass, sales gold) automatically. */
-.cg-shell{display:flex;align-items:flex-start;}
-.cg-rail{flex:0 0 250px;align-self:stretch;border-right:1px solid var(--cg-line,#2a313c);background:var(--cg-railbg,rgba(0,0,0,.18));position:sticky;top:0;max-height:100vh;overflow-y:auto;padding-bottom:30px;}
-.cg-main{flex:1 1 auto;min-width:0;--cg-cols:3;}
-.cg-sec{padding:12px 15px;border-bottom:1px solid var(--cg-line,#2a313c);}
-.cg-sec h4{margin:0 0 9px;font-size:11px;text-transform:uppercase;letter-spacing:.6px;color:var(--cg-mut,#8b949e);cursor:pointer;display:flex;align-items:center;justify-content:space-between;user-select:none;}
-.cg-sec h4::after{content:'▾';font-size:11px;margin-left:8px;transition:transform .15s;}
-.cg-sec.collapsed h4::after{transform:rotate(-90deg);}
-.cg-sec.collapsed > *:not(h4){display:none !important;}
-.cg-chips{display:flex;flex-wrap:wrap;gap:6px;}
-.cg-chip{background:var(--cg-card,#161b22);color:var(--cg-mut,#8b949e);border:1px solid var(--cg-line,#2a313c);border-radius:16px;padding:4px 10px;font-size:12px;cursor:pointer;user-select:none;}
-.cg-chip:hover{color:var(--cg-ink,#e6edf3);}
-.cg-chip.active{border-color:var(--cg-acc,#58a6ff);color:var(--cg-acc,#58a6ff);background:var(--cg-accbg,rgba(88,166,255,.10));}
-.cg-ct{color:var(--cg-mut,#8b949e);font-size:10px;margin-left:4px;}
-.cg-row2{display:flex;gap:8px;}
-.cg-fld{flex:1;}
-.cg-fld label{display:block;font-size:10px;color:var(--cg-mut,#8b949e);margin-bottom:3px;}
-.cg-fld input{width:100%;background:var(--cg-card,#161b22);color:var(--cg-ink,#e6edf3);border:1px solid var(--cg-line,#2a313c);border-radius:8px;padding:7px 9px;font-size:13px;}
-.cg-ftogs{display:flex;flex-direction:column;gap:1px;max-height:300px;overflow-y:auto;}
-.cg-ftog{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--cg-ink,#e6edf3);cursor:pointer;padding:3px 4px;border-radius:6px;}
-.cg-ftog:hover{background:var(--cg-accbg,rgba(88,166,255,.06));}
-.cg-mini{margin-top:8px;display:flex;gap:5px;}
-.cg-mini button{background:var(--cg-card,#161b22);color:var(--cg-mut,#8b949e);border:1px solid var(--cg-line,#2a313c);border-radius:7px;padding:4px 9px;font-size:11px;cursor:pointer;}
-.cg-mini button:hover{border-color:var(--cg-acc,#58a6ff);color:var(--cg-ink,#e6edf3);}
-.cg-top{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:13px 18px;border-bottom:1px solid var(--cg-line,#2a313c);position:sticky;top:0;background:var(--cg-topbg,rgba(14,17,22,.94));backdrop-filter:blur(6px);z-index:6;}
-.cg-q{min-width:240px;background:var(--cg-card,#161b22);color:var(--cg-ink,#e6edf3);border:1px solid var(--cg-line,#2a313c);border-radius:8px;padding:8px 11px;font-size:13px;}
-.cg-seg{display:inline-flex;border:1px solid var(--cg-line,#2a313c);border-radius:8px;overflow:hidden;}
-.cg-seg button{background:var(--cg-card,#161b22);color:var(--cg-mut,#8b949e);border:0;border-right:1px solid var(--cg-line,#2a313c);padding:7px 12px;font-size:12px;cursor:pointer;}
-.cg-seg button:last-child{border-right:0;}
-.cg-seg button.active{background:var(--cg-acc,#1f6feb);color:#fff;}
-.cg-dens{font-size:12px;color:var(--cg-mut,#8b949e);display:flex;align-items:center;gap:7px;}
-.cg-dens input{accent-color:var(--cg-acc,#58a6ff);}
-.cg-count{margin-left:auto;font-size:12px;color:var(--cg-mut,#8b949e);}
-.cg-count b{color:var(--cg-ink,#e6edf3);}
-.cg-grid{display:grid;gap:16px;padding:18px;grid-template-columns:repeat(var(--cg-cols,3),minmax(0,1fr));}
-.cg-grid.cg-listhost{display:block;}
-.cg-card{background:var(--cg-card,#161b22);border:1px solid var(--cg-line,#2a313c);border-radius:12px;padding:15px;display:flex;flex-direction:column;gap:5px;}
-.cg-card.red{border-color:var(--cg-red,#f85149);}
-.cg-name{font-weight:600;font-size:14px;color:var(--cg-ink,#e6edf3);}
-.cg-sub{font-size:12px;color:var(--cg-mut,#8b949e);}
-.cg-r{display:flex;justify-content:space-between;font-size:12px;gap:10px;}
-.cg-r .cg-k{color:var(--cg-mut,#8b949e);}
-.cg-empty{padding:50px;text-align:center;color:var(--cg-mut,#8b949e);}
-.cg-listwrap{overflow-x:auto;margin:18px;border:1px solid var(--cg-line,#2a313c);border-radius:12px;}
-.cg-tbl{width:100%;border-collapse:collapse;font-size:12.5px;white-space:nowrap;}
-.cg-tbl th,.cg-tbl td{text-align:right;padding:9px 13px;border-top:1px solid var(--cg-line,#2a313c);}
-.cg-tbl th{position:sticky;top:0;background:var(--cg-railbg,#0b0e13);color:var(--cg-mut,#8b949e);font-size:10px;text-transform:uppercase;letter-spacing:.5px;border-top:0;}
-.cg-tbl td.cg-stick,.cg-tbl th.cg-stick{text-align:left;position:sticky;left:0;background:var(--cg-card,#161b22);}
-.cg-tbl th.cg-stick{background:var(--cg-railbg,#0b0e13);}
-.cg-tbl tr:hover td{background:var(--cg-accbg,rgba(88,166,255,.06));}
-.cg-tbl tr.red td{background:rgba(248,81,73,.07);}
-.cg-la{font-weight:600;color:var(--cg-ink,#e6edf3);}
-.cg-lc{color:var(--cg-mut,#8b949e);font-size:11px;}
-@media(max-width:820px){.cg-rail{display:none;}}
diff --git a/public/crcp-grid.js.pre-3view.bak b/public/crcp-grid.js.pre-3view.bak
deleted file mode 100644
index 1bfc75d..0000000
--- a/public/crcp-grid.js.pre-3view.bak
+++ /dev/null
@@ -1,191 +0,0 @@
-/* crcp-grid.js — reusable Amazon-style faceted grid engine for the CRE fleet.
- * Config-driven, theme-agnostic (inherits the host page's CSS vars via cg-grid.css).
- * One page = one CrcpGrid.mount(opts) call. Mirrors the proven condos.html engine so
- * every list page gets the SAME behavior: left-rail facets, per-field show/hide toggles,
- * grid/list view toggle, density slider, live search, localStorage persistence.
- *
- * opts = {
- * data: [row,...], // all rows (client-side faceting)
- * fields: [{k,l,def,col,badge,money,num,date,calc,suf,pct}], // every data point (card rows + list cols)
- * facets: [{k,l,type:'chip'|'range',calc,order,max}], // rail filters (auto-derived if omitted)
- * mount: '#cgMain', // container for topbar + grid
- * rail: '#cgRail', // container for the left rail
- * title: row => ({name, sub}),// card/list title + subline
- * search: row => 'text blob', // searchable string
- * badgeHtml: row => '<span>...'|'',// optional status badge html (card + list)
- * storageKey:'fha', // localStorage namespace
- * cols: 3, // default density
- * onRow: row => ({cls}), // optional per-row class hook (e.g. red flag)
- * }
- */
-(function (global) {
- const esc = s => String(s == null ? '' : s).replace(/&/g, '&').replace(/</g, '<').replace(/"/g, '"');
- const $ = (sel, root) => (root || document).querySelector(sel);
-
- function fmt(v, f) {
- if (v == null || v === '') return '—';
- if (f.money) return '$' + (+v).toLocaleString() + (f.suf || '');
- if (f.pct) return (+v).toFixed(2) + '%';
- if (f.num) return (+v).toLocaleString() + (f.suf || '');
- if (f.date) { try { return new Date(v).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }); } catch (e) { return '—'; } }
- return esc(v) + (f.suf || '');
- }
-
- function CrcpGrid(opts) {
- const O = opts;
- const KEY = O.storageKey || 'cg';
- const fields = O.fields;
- const data = O.data || [];
- const fval = (row, f) => f.calc ? f.calc(row) : row[f.k];
-
- // ---- field visibility (persisted) ----
- const VIS = (function () {
- let s = {}; try { s = JSON.parse(localStorage.getItem(KEY + 'Fields') || '{}'); } catch (e) {}
- const o = {}; fields.forEach(f => o[f.k] = (f.k in s) ? !!s[f.k] : !!f.def); return o;
- })();
- const saveVis = () => { try { localStorage.setItem(KEY + 'Fields', JSON.stringify(VIS)); } catch (e) {} };
-
- // ---- auto-derive facets if not provided ----
- let facets = O.facets;
- if (!facets) {
- facets = [];
- fields.forEach(f => {
- if (f.badge || f.k === 'title') return;
- const vals = data.map(r => fval(r, f)).filter(v => v != null && v !== '');
- if (!vals.length) return;
- const numeric = f.money || f.num || f.pct || vals.every(v => typeof v === 'number' || /^\d+(\.\d+)?$/.test(v));
- const uniq = new Set(vals.map(String));
- if (!numeric && uniq.size <= 24) facets.push({ k: f.k, l: f.l, type: 'chip', calc: f.calc });
- else if (numeric) facets.push({ k: f.k, l: f.l, type: 'range', calc: f.calc, money: f.money });
- });
- }
-
- // ---- filter state ----
- const F = { q: '', chips: {}, ranges: {} };
- facets.forEach(fc => { if (fc.type === 'chip') F.chips[fc.k] = new Set(); else F.ranges[fc.k] = { min: null, max: null }; });
-
- const gv = (row, fc) => fc.calc ? fc.calc(row) : row[fc.k];
- function pass(row) {
- for (const fc of facets) {
- if (fc.type === 'chip') { const set = F.chips[fc.k]; if (set.size && !set.has(String(gv(row, fc)))) return false; }
- else { const r = F.ranges[fc.k], v = gv(row, fc);
- if (r.min != null && !(+v >= r.min)) return false;
- if (r.max != null && !(+v <= r.max)) return false; }
- }
- if (F.q) { const blob = (O.search ? O.search(row) : fields.map(f => fval(row, f)).join(' ')).toLowerCase(); if (!blob.includes(F.q)) return false; }
- return true;
- }
-
- // ---- rail ----
- function buildRail() {
- const rail = $(O.rail);
- let html = '';
- facets.forEach(fc => {
- if (fc.type === 'chip') {
- const counts = {};
- data.forEach(r => { const v = gv(r, fc); if (v != null && v !== '') counts[v] = (counts[v] || 0) + 1; });
- const ent = Object.entries(counts).sort((a, b) => b[1] - a[1]).slice(0, fc.max || 20);
- if (!ent.length) return;
- html += `<div class="cg-sec collapsed"><h4>${esc(fc.l)}</h4><div class="cg-chips" data-facet="${esc(fc.k)}">` +
- ent.map(([k, c]) => `<span class="cg-chip" data-v="${esc(k)}">${esc(k)}<span class="cg-ct">${c}</span></span>`).join('') + `</div></div>`;
- } else {
- html += `<div class="cg-sec collapsed"><h4>${esc(fc.l)}</h4><div class="cg-row2" data-facet="${esc(fc.k)}">` +
- `<div class="cg-fld"><label>Min</label><input type="number" class="cg-min" placeholder="any"></div>` +
- `<div class="cg-fld"><label>Max</label><input type="number" class="cg-max" placeholder="any"></div></div></div>`;
- }
- });
- html += `<div class="cg-sec"><h4>Fields — show / hide</h4><div class="cg-ftogs">` +
- fields.map(f => `<label class="cg-ftog"><input type="checkbox" data-fk="${esc(f.k)}" ${VIS[f.k] ? 'checked' : ''}><span>${esc(f.l)}</span>${f.col ? '<span class="cg-ct" title="List column">▤</span>' : ''}</label>`).join('') +
- `</div><div class="cg-mini"><button data-all="1">All</button><button data-none="1">None</button><button data-def="1">Default</button></div></div>`;
- rail.innerHTML = html;
-
- // wire facets
- rail.querySelectorAll('.cg-chips').forEach(box => box.addEventListener('click', e => {
- const c = e.target.closest('.cg-chip'); if (!c) return;
- const set = F.chips[box.dataset.facet]; const v = c.dataset.v;
- set.has(v) ? set.delete(v) : set.add(v); c.classList.toggle('active'); render();
- }));
- rail.querySelectorAll('.cg-row2').forEach(box => {
- const k = box.dataset.facet;
- box.querySelector('.cg-min').addEventListener('input', e => { F.ranges[k].min = e.target.value ? +e.target.value : null; render(); });
- box.querySelector('.cg-max').addEventListener('input', e => { F.ranges[k].max = e.target.value ? +e.target.value : null; render(); });
- });
- // field toggles
- rail.querySelector('.cg-ftogs').addEventListener('change', e => { const cb = e.target.closest('input[data-fk]'); if (!cb) return; VIS[cb.dataset.fk] = cb.checked; saveVis(); render(); });
- const mini = rail.querySelector('.cg-mini');
- mini.addEventListener('click', e => {
- const b = e.target.closest('button'); if (!b) return;
- if (b.dataset.all) fields.forEach(f => VIS[f.k] = true);
- else if (b.dataset.none) fields.forEach(f => VIS[f.k] = false);
- else if (b.dataset.def) fields.forEach(f => VIS[f.k] = !!f.def);
- saveVis(); buildRail(); render();
- });
- // collapsible headers
- rail.querySelectorAll('.cg-sec > h4').forEach(h => h.addEventListener('click', () => h.parentElement.classList.toggle('collapsed')));
- }
-
- // ---- renderers ----
- function cards(rows) {
- const vis = fields.filter(f => VIS[f.k] && !f.badge && f.k !== 'title');
- return rows.map(r => {
- const t = O.title ? O.title(r) : { name: r.name || '', sub: '' };
- const rc = O.onRow ? (O.onRow(r).cls || '') : '';
- const nm = t.href ? `<a href="${esc(t.href)}" target="_blank" rel="noopener noreferrer" style="color:inherit">${esc(t.name)}</a>` : esc(t.name);
- return `<div class="cg-card ${rc}">` +
- `<div class="cg-name">${nm}</div>${t.sub ? `<div class="cg-sub">${esc(t.sub)}</div>` : ''}` +
- (O.badgeHtml ? O.badgeHtml(r) : '') +
- vis.map(f => `<div class="cg-r"><span class="cg-k">${esc(f.l)}</span><span>${fmt(fval(r, f), f)}</span></div>`).join('') +
- `</div>`;
- }).join('');
- }
- function list(rows) {
- const cols = fields.filter(f => f.col && VIS[f.k] && !f.badge);
- const head = `<th class="cg-stick">${esc((O.titleCol || 'Name'))}</th>` + cols.map(f => `<th>${esc(f.l)}</th>`).join('');
- const body = rows.map(r => {
- const t = O.title ? O.title(r) : { name: '', sub: '' };
- const rc = O.onRow ? (O.onRow(r).cls || '') : '';
- const nm = t.href ? `<a href="${esc(t.href)}" target="_blank" rel="noopener noreferrer" style="color:inherit">${esc(t.name)}</a>` : esc(t.name);
- return `<tr class="${rc}"><td class="cg-stick"><span class="cg-la">${nm}</span>${t.sub ? `<div class="cg-lc">${esc(t.sub)}</div>` : ''}</td>` +
- cols.map(f => `<td>${fmt(fval(r, f), f)}</td>`).join('') + `</tr>`;
- }).join('');
- return `<div class="cg-listwrap"><table class="cg-tbl"><thead><tr>${head}</tr></thead><tbody>${body}</tbody></table></div>`;
- }
-
- let VIEW = localStorage.getItem(KEY + 'View') || O.view || 'grid';
- const CAP = O.cap || 1500; // DOM render cap for perf; count still reflects the full match set
- function render() {
- const rows = data.filter(pass);
- const shown = rows.length > CAP ? rows.slice(0, CAP) : rows;
- const g = $(O.mount + ' .cg-grid');
- if (!rows.length) { g.className = 'cg-grid'; g.innerHTML = '<div class="cg-empty">no matches for these filters.</div>'; }
- else if (VIEW === 'list') { g.className = 'cg-grid cg-listhost'; g.innerHTML = list(shown); }
- else { g.className = 'cg-grid'; g.innerHTML = cards(shown); }
- const cnt = $(O.mount + ' .cg-count');
- if (cnt) cnt.innerHTML = `<b>${rows.length.toLocaleString()}</b> of ${data.length.toLocaleString()}` + (rows.length > CAP ? ` <span style="opacity:.7">(first ${CAP.toLocaleString()} shown)</span>` : '');
- }
-
- // ---- topbar ----
- function buildTop() {
- const m = $(O.mount);
- m.insertAdjacentHTML('afterbegin',
- `<div class="cg-top">` +
- `<input type="text" class="cg-q" placeholder="search…">` +
- `<span class="cg-seg"><button data-view="grid" class="${VIEW === 'grid' ? 'active' : ''}">▦ Grid</button><button data-view="list" class="${VIEW === 'list' ? 'active' : ''}">☰ List</button></span>` +
- `<label class="cg-dens">Density <input type="range" class="cg-cols" min="1" max="12" step="1" value="${O.cols || 3}"></label>` +
- `<span class="cg-count"></span></div><div class="cg-grid"></div>`);
- const q = m.querySelector('.cg-q');
- q.addEventListener('input', () => { clearTimeout(q._t); q._t = setTimeout(() => { F.q = q.value.trim().toLowerCase(); render(); }, 160); });
- m.querySelector('.cg-seg').addEventListener('click', e => { const b = e.target.closest('button[data-view]'); if (!b) return; VIEW = b.dataset.view; localStorage.setItem(KEY + 'View', VIEW); m.querySelectorAll('.cg-seg button').forEach(x => x.classList.toggle('active', x.dataset.view === VIEW)); render(); });
- const cols = m.querySelector('.cg-cols');
- const setCols = v => { m.style.setProperty('--cg-cols', v); localStorage.setItem(KEY + 'Cols', v); };
- cols.addEventListener('input', e => setCols(e.target.value));
- const sc = localStorage.getItem(KEY + 'Cols'); if (sc) { cols.value = sc; }
- setCols(cols.value);
- }
-
- buildTop(); buildRail(); render();
- return { render, setData(d) { O.data.length = 0; d.forEach(x => O.data.push(x)); buildRail(); render(); } };
- }
-
- global.CrcpGrid = { mount: opts => new CrcpGrid(opts) };
-})(window);
← 6c1c45b auto-data-snapshot: 2026-08-13T09:23:12 (2 data files) — dat
·
back to Commercialrealestate
·
auto-data-snapshot: 2026-08-13T09:53:42 (2 data files) — pub 8437e2e →