[object Object]

← back to Consulting Rentv Com

auto-save: 2026-07-26T07:41:30 (2 files) — public/admin/index.html public/admin/lhf.js

290685b150191bc1a7445ccccd51a2b37c16f401 · 2026-07-26 07:41:30 -0700 · Steve Abrams

Files touched

Diff

commit 290685b150191bc1a7445ccccd51a2b37c16f401
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun Jul 26 07:41:30 2026 -0700

    auto-save: 2026-07-26T07:41:30 (2 files) — public/admin/index.html public/admin/lhf.js
---
 public/admin/index.html | 56 ++---------------------------------------
 public/admin/lhf.js     | 66 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 68 insertions(+), 54 deletions(-)

diff --git a/public/admin/index.html b/public/admin/index.html
index 8025b14..9c64fc8 100644
--- a/public/admin/index.html
+++ b/public/admin/index.html
@@ -28,60 +28,8 @@
     <div><a href="/portal">← Deliverable</a> &nbsp; <a href="/">Sign-in</a></div>
   </header>
 
-  <!-- Low Hanging Fruit — reusable ranked read-only view over /api/admin/suggestions.
-       ROI = impact/effort; time from effort; quick wins (low effort + high impact) flagged. -->
-  <section id="lhf">
-    <style>
-      #lhf{margin:20px 24px 4px;background:#1c1915;border:1px solid #2c2823;border-radius:6px;padding:18px 20px}
-      #lhf .lhf-h{display:flex;justify-content:space-between;align-items:baseline;gap:12px;flex-wrap:wrap;margin-bottom:2px}
-      #lhf .lhf-h h2{font-size:13px;letter-spacing:.14em;text-transform:uppercase;color:#c6a765;margin:0}
-      #lhf .lhf-sub{color:#8a8272;font-size:12px}
-      #lhf ol{list-style:none;margin:14px 0 0;padding:0;display:grid;gap:10px}
-      #lhf li{display:grid;grid-template-columns:34px 1fr;gap:12px;background:#161310;border:1px solid #2c2823;border-radius:5px;padding:12px 14px}
-      #lhf li.fruit{border-color:#c6a765}
-      #lhf .rk{font-size:20px;font-weight:800;color:#c6a765;line-height:1.1}
-      #lhf .ti{font-weight:600;color:#e9e2d6}
-      #lhf .badges{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0}
-      #lhf .b{font-size:10px;letter-spacing:.05em;text-transform:uppercase;padding:3px 8px;border-radius:3px;border:1px solid #2c2823;color:#b8ad97}
-      #lhf .b.roi-high,#lhf .b.time-quick{color:#7ad17a;border-color:#3a5a3a}
-      #lhf .b.roi-solid{color:#c6a765}
-      #lhf .b.fruit{background:#c6a765;color:#12100e;border:0;font-weight:700}
-      #lhf .rat{font-size:12.5px;color:#b8ad97;margin-top:2px}
-      #lhf .why{font-size:12.5px;color:#8a8272;margin-top:4px}
-      #lhf .why b{color:#c6a765;font-weight:600}
-      #lhf .empty{color:#8a8272;font-size:13px}
-    </style>
-    <div class="lhf-h">
-      <h2>⚡ Low Hanging Fruit</h2>
-      <span class="lhf-sub">ranked by ROI ÷ time — fastest, highest-return moves first</span>
-    </div>
-    <p style="color:#8a8272;font-size:12px;margin:8px 0 0">⚡ <b style="color:#c6a765">Quick wins</b> (low effort · high impact) leverage assets you already have — activate, don’t rebuild. That is why they are simple.</p>
-    <ol id="lhf-list"><li class="empty">Loading growth moves…</li></ol>
-    <script>
-    (function(){
-      var TIME=function(e){return e<=2?['Quick','time-quick']:e===3?['Moderate','time-mod']:['Involved','time-inv'];};
-      var ROI=function(i,e){var r=i/Math.max(1,e);return r>=2?['High','roi-high']:r>=1.2?['Solid','roi-solid']:['Low','roi-low'];};
-      function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];});}
-      fetch('/api/admin/suggestions').then(function(r){return r.ok?r.json():[];}).then(function(list){
-        var el=document.getElementById('lhf-list');
-        list=(list||[]).filter(function(s){return s&&s.title;});
-        if(!list.length){el.innerHTML='<li class="empty">No growth moves yet — add ideas in the Growth Moves panel below.</li>';return;}
-        list.forEach(function(s){s._i=Number(s.impact)||0;s._e=Number(s.effort)||3;s._roi=s._i/Math.max(1,s._e);s._fruit=s._e<=2&&s._i>=4;});
-        list.sort(function(a,b){return b._roi-a._roi||a._e-b._e||b._i-a._i;});
-        el.innerHTML=list.map(function(s,idx){
-          var t=TIME(s._e),ro=ROI(s._i,s._e);
-          return `<li class="${s._fruit?'fruit':''}"><div class="rk">${idx+1}</div><div>`+
-            `<div class="ti">${esc(s.title)}</div>`+
-            `<div class="badges">${s._fruit?'<span class="b fruit">⚡ Quick win</span>':''}`+
-            `<span class="b ${ro[1]}">ROI ${s._roi.toFixed(1)}</span><span class="b ${t[1]}">⏱ ${t[0]}</span>`+
-            `${s.category?`<span class="b">${esc(s.category)}</span>`:''}`+
-            `<span class="b">impact ${s._i} · effort ${s._e}</span></div>`+
-            `${s.rationale?`<div class="rat">${esc(s.rationale)}</div>`:''}</div></li>`;
-        }).join('');
-      }).catch(function(){document.getElementById('lhf-list').innerHTML='<li class="empty">Could not load suggestions.</li>';});
-    })();
-    </script>
-  </section>
+  <section id="lhf"></section>
+<script src="/admin/lhf.js"></script>
 
   <div class="wrap" id="wrap"></div>
 
diff --git a/public/admin/lhf.js b/public/admin/lhf.js
new file mode 100644
index 0000000..216e7c9
--- /dev/null
+++ b/public/admin/lhf.js
@@ -0,0 +1,66 @@
+// Low Hanging Fruit — canonical ranked read-only widget over /api/admin/suggestions.
+// SOURCE OF TRUTH. Copied verbatim into each portal's served static dir
+// (rentv: public/admin/lhf.js -> /admin/lhf.js ; car-wash: public/assets/lhf.js -> /assets/lhf.js).
+// Mount with:  <section id="lhf"></section><script src="<served-path>/lhf.js"></script>
+// Ranks the suggestions bucket by ROI = impact/effort and time = effort; flags the
+// low-effort + high-impact "quick wins". Read-only; no data mutation.
+(function () {
+  var host = document.getElementById('lhf');
+  if (!host) return;
+
+  if (!document.getElementById('lhf-style')) {
+    var st = document.createElement('style');
+    st.id = 'lhf-style';
+    st.textContent =
+      '#lhf{margin:20px 24px 4px;background:#1c1915;border:1px solid #2c2823;border-radius:6px;padding:18px 20px}' +
+      '#lhf .lhf-h{display:flex;justify-content:space-between;align-items:baseline;gap:12px;flex-wrap:wrap;margin-bottom:2px}' +
+      '#lhf .lhf-h h2{font-size:13px;letter-spacing:.14em;text-transform:uppercase;color:#c6a765;margin:0}' +
+      '#lhf .lhf-sub{color:#8a8272;font-size:12px}' +
+      '#lhf .lhf-note{color:#8a8272;font-size:12px;margin:8px 0 0}#lhf .lhf-note b{color:#c6a765}' +
+      '#lhf ol{list-style:none;margin:14px 0 0;padding:0;display:grid;gap:10px}' +
+      '#lhf li{display:grid;grid-template-columns:34px 1fr;gap:12px;background:#161310;border:1px solid #2c2823;border-radius:5px;padding:12px 14px}' +
+      '#lhf li.fruit{border-color:#c6a765}' +
+      '#lhf .rk{font-size:20px;font-weight:800;color:#c6a765;line-height:1.1}' +
+      '#lhf .ti{font-weight:600;color:#e9e2d6}' +
+      '#lhf .badges{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0}' +
+      '#lhf .b{font-size:10px;letter-spacing:.05em;text-transform:uppercase;padding:3px 8px;border-radius:3px;border:1px solid #2c2823;color:#b8ad97}' +
+      '#lhf .b.roi-high,#lhf .b.time-quick{color:#7ad17a;border-color:#3a5a3a}' +
+      '#lhf .b.roi-solid{color:#c6a765}' +
+      '#lhf .b.fruit{background:#c6a765;color:#12100e;border:0;font-weight:700}' +
+      '#lhf .rat{font-size:12.5px;color:#b8ad97;margin-top:2px}' +
+      '#lhf .empty{color:#8a8272;font-size:13px}';
+    document.head.appendChild(st);
+  }
+
+  host.innerHTML =
+    '<div class="lhf-h"><h2>⚡ Low Hanging Fruit</h2>' +
+    '<span class="lhf-sub">ranked by ROI ÷ time — fastest, highest-return moves first</span></div>' +
+    '<p class="lhf-note">⚡ <b>Quick wins</b> (low effort · high impact) leverage assets you already have — activate, don’t rebuild. That is why they are simple.</p>' +
+    '<ol id="lhf-list"><li class="empty">Loading growth moves…</li></ol>';
+
+  function esc(s) {
+    return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) {
+      return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c];
+    });
+  }
+  function TIME(e) { return e <= 2 ? ['Quick', 'time-quick'] : e === 3 ? ['Moderate', 'time-mod'] : ['Involved', 'time-inv']; }
+  function ROI(i, e) { var r = i / Math.max(1, e); return r >= 2 ? ['High', 'roi-high'] : r >= 1.2 ? ['Solid', 'roi-solid'] : ['Low', 'roi-low']; }
+
+  fetch('/api/admin/suggestions').then(function (r) { return r.ok ? r.json() : []; }).then(function (list) {
+    var el = document.getElementById('lhf-list');
+    list = (list || []).filter(function (s) { return s && s.title; });
+    if (!list.length) { el.innerHTML = '<li class="empty">No growth moves yet — add ideas in the Growth Moves panel below.</li>'; return; }
+    list.forEach(function (s) { s._i = Number(s.impact) || 0; s._e = Number(s.effort) || 3; s._roi = s._i / Math.max(1, s._e); s._fruit = s._e <= 2 && s._i >= 4; });
+    list.sort(function (a, b) { return b._roi - a._roi || a._e - b._e || b._i - a._i; });
+    el.innerHTML = list.map(function (s, idx) {
+      var t = TIME(s._e), ro = ROI(s._i, s._e);
+      return '<li class="' + (s._fruit ? 'fruit' : '') + '"><div class="rk">' + (idx + 1) + '</div><div>' +
+        '<div class="ti">' + esc(s.title) + '</div>' +
+        '<div class="badges">' + (s._fruit ? '<span class="b fruit">⚡ Quick win</span>' : '') +
+        '<span class="b ' + ro[1] + '">ROI ' + s._roi.toFixed(1) + '</span><span class="b ' + t[1] + '">⏱ ' + t[0] + '</span>' +
+        (s.category ? '<span class="b">' + esc(s.category) + '</span>' : '') +
+        '<span class="b">impact ' + s._i + ' · effort ' + s._e + '</span></div>' +
+        (s.rationale ? '<div class="rat">' + esc(s.rationale) + '</div>' : '') + '</div></li>';
+    }).join('');
+  }).catch(function () { document.getElementById('lhf-list').innerHTML = '<li class="empty">Could not load suggestions.</li>'; });
+})();

← b26f75e lhf: contrarian fixes — kill repetitive why-filler (panel no  ·  back to Consulting Rentv Com  ·  auto-save: 2026-07-26T08:41:50 (2 files) — server.js public/ aa47540 →