[object Object]

← back to Consulting Rentv Com

rentv portal: SWOT agent + section, intake CRM-module checklist (mirrors hamburger)

84f3caa0e21a747340e1a56f65d628043cf0b23e · 2026-07-17 21:33:27 -0700 · Steve

Files touched

Diff

commit 84f3caa0e21a747340e1a56f65d628043cf0b23e
Author: Steve <steve@designerwallcoverings.com>
Date:   Fri Jul 17 21:33:27 2026 -0700

    rentv portal: SWOT agent + section, intake CRM-module checklist (mirrors hamburger)
---
 build.mjs          | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
 public/intake.html | 28 ++++++++++++++++++++++++++
 public/portal.html | 17 +++++++++++++++-
 server.js          | 21 +++++++++++++++++--
 4 files changed, 121 insertions(+), 4 deletions(-)

diff --git a/build.mjs b/build.mjs
index 2c6424c..5a82a3c 100644
--- a/build.mjs
+++ b/build.mjs
@@ -257,7 +257,7 @@ const partners = [
 ];
 const partnerRows = partners.map((p,i)=>`<div class="pcard rv"><div class="pnum">${String(i+1).padStart(2,'0')}</div><div><h4>${p[0]}</h4><p>${p[1]}</p></div></div>`).join('');
 
-const CATS = [['summary','Executive Summary'],['reputation','Reputation & Authority'],['empire','The Empire & Sublease.com'],['competitors','Competitors'],['advertising','Where to Advertise'],['timing','Timing & Cadence'],['audit','Website Audit'],['teardown','Page-by-Page Teardown'],['social','Social Media Audit'],['suggested','Suggested New Websites'],['partners','Unlikely Partners'],['revenue','Unlikely Revenue'],['videos','Video + Brand Films'],['dealdesk','Live Deal Desk (demo)'],['roadmap','90-Day Roadmap']];
+const CATS = [['summary','Executive Summary'],['swot','SWOT Analysis'],['reputation','Reputation & Authority'],['empire','The Empire & Sublease.com'],['competitors','Competitors'],['advertising','Where to Advertise'],['timing','Timing & Cadence'],['audit','Website Audit'],['teardown','Page-by-Page Teardown'],['social','Social Media Audit'],['suggested','Suggested New Websites'],['partners','Unlikely Partners'],['revenue','Unlikely Revenue'],['videos','Video + Brand Films'],['dealdesk','Live Deal Desk (demo)'],['roadmap','90-Day Roadmap']];
 
 /* ---- Live Deal Desk (real CRCP data) --------------------------------------- */
 const DESK_DEALS = (CRCP.deals || []).filter(d => d.score != null).slice(0, 24);
@@ -271,6 +271,53 @@ const dealCards = DESK_DEALS.map(d => `
     <div class="dbody"><div class="daddr">${d.address}</div><div class="dcity">${d.city} · ${d.type}${d.units ? ' · ' + d.units + ' units' : ''}</div>
     <div class="dnums"><span>$${(d.price/1e6).toFixed(2)}M</span>${d.cap_rate ? `<span>${d.cap_rate.toFixed(1)}% cap</span>` : ''}${d.dscr ? `<span>${d.dscr.toFixed(2)} DSCR</span>` : ''}${d.coc ? `<span>${d.coc.toFixed(1)}% CoC</span>` : ''}</div></div>
   </div>`).join('');
+/* ---- SWOT agent -----------------------------------------------------------
+ * A self-contained "agent" that runs the four SWOT lenses over the client's
+ * profile and emits a 2x2 strategic snapshot. Derives Strengths/Weaknesses from
+ * the reputation read, Opportunities from the revenue + partner playbook, and
+ * Threats from the competitive landscape — so the quadrant stays consistent with
+ * the rest of the brief. Reusable: swap SWOT_INPUT per client and it re-runs. */
+const SWOT_INPUT = {
+  strengths: [
+    '25-year brand & archive — trusted CRE authority since 1998',
+    'State of the Market conference franchise ($175 ticket, live audience)',
+    'CRE Talk — 19+ sponsored CEO episodes',
+    'The REview video platform + an active email operation',
+    'Owns sublease.com (relaunch plan in hand)',
+    'Deep, defensible Western-U.S. focus',
+  ],
+  weaknesses: [
+    'Dated, pre-mobile website',
+    'Social accounts dormant & unlinked (LinkedIn 818 followers, idle)',
+    'No visible newsletter capture — audience not owned',
+    'No paid distribution',
+    'Three properties siloed — no cross-linking, separate lists',
+  ],
+  opportunities: [
+    'Daily newsletter — convert 25 years of authority into owned audience',
+    'Relaunch sublease.com as a marketplace attached to the media brand',
+    'Recurring plans to brokers / agents / broker-dealers ($99–$900+/mo)',
+    'Package video + brand films for sponsors',
+    'Referral engines: lenders, PropTech, universities, EDC orgs',
+  ],
+  threats: [
+    'National machines out-distribute regionally (Bisnow, CoStar/LoopNet, Crexi, The Real Deal)',
+    'Younger CRE pros discover news on other platforms',
+    'Platform algorithm shifts & ad-blocking erode reach',
+    'CRE downturn softens conference & sponsor spend',
+  ],
+};
+function swotAgent(input){
+  const quad = (key,title,cls) => `<div class="swotq ${cls}"><h3>${title}</h3><ul>${(input[key]||[]).map(x=>`<li>${x}</li>`).join('')}</ul></div>`;
+  return `<div class="swotgrid">
+    ${quad('strengths','Strengths','pos')}
+    ${quad('weaknesses','Weaknesses','neg')}
+    ${quad('opportunities','Opportunities','pos')}
+    ${quad('threats','Threats','neg')}
+  </div>`;
+}
+const swotGrid = swotAgent(SWOT_INPUT);
+
 const navLinks = CATS.map(c=>`<a href="#${c[0]}">${c[1]}</a>`).join('');
 const heroSlides = HERO_IMGS.map((u,i)=>`<div class="slide${i===0?' on':''}" style="background-image:url('${u}')"></div>`).join('');
 
@@ -336,6 +383,11 @@ td.mo{font-family:'Cormorant Garamond',serif;color:var(--gold);font-size:18px;wi
 .cards2{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1px;background:var(--line);margin-top:34px}
 .cards2 .c{background:var(--bg);padding:32px 28px}.cards2 .c h3{font-family:'Cormorant Garamond',serif;font-size:22px;color:var(--gold);margin-bottom:10px}.cards2 .c p{color:var(--dim);font-size:16px;line-height:1.6}
 .flag{color:#e8a0a0}.good{color:#9fd6b0}
+.swotgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:1px;background:var(--line);margin-top:34px}
+.swotq{background:var(--bg);padding:28px 26px}.swotq h3{font-family:'Cormorant Garamond',serif;font-size:22px;margin-bottom:12px}
+.swotq.pos{border-top:2px solid #9fd6b0}.swotq.neg{border-top:2px solid #e8a0a0}
+.swotq.pos h3{color:#9fd6b0}.swotq.neg h3{color:#e8a0a0}
+.swotq ul{margin:0;padding-left:18px}.swotq li{color:var(--dim);font-size:15px;line-height:1.55;margin:6px 0}
 .flagbox{display:flex;gap:22px;margin-top:34px;padding:30px 32px;border:1px solid var(--gold);background:rgba(200,161,90,.07)}
 .flagbox .i{font-size:30px;color:var(--gold)}.flagbox h3{font-family:'Cormorant Garamond',serif;font-size:24px;color:var(--gold);margin-bottom:10px}.flagbox p{color:var(--ink);opacity:.92;font-size:16px;line-height:1.6}
 .vgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:20px;margin-top:20px}
@@ -416,6 +468,11 @@ footer{padding:56px clamp(24px,7vw,120px);border-top:1px solid var(--line);color
   <div class="cards2"><div class="c"><h3>Strengths</h3><p class="good">25-year archive &amp; authority · the State of the Market conference franchise · CRE Talk (19+ CEO episodes, sponsored) · The REview video platform · an active email operation · deep Western-U.S. focus.</p></div><div class="c"><h3>Watch-outs</h3><p class="flag">Dated pre-mobile site · social dormant & unlinked · no newsletter capture visible · no paid distribution · younger CRE pros discover news elsewhere.</p></div><div class="c"><h3>Action</h3><p>Modernize the site, turn on a daily newsletter, activate LinkedIn — convert 25 years of authority into owned audience & recurring revenue.</p></div></div>
 </div></section>
 
+<section id="swot"><div class="eyebrow">Strategic Snapshot · SWOT</div><h2 class="disp">The whole board on one screen.</h2><div class="accbody">
+  <p class="lead2">The SWOT agent runs four lenses over RENTV's profile — internal <b>Strengths</b> and <b>Weaknesses</b>, external <b>Opportunities</b> and <b>Threats</b> — so the strategy that follows has a single, honest starting picture.</p>
+  ${swotGrid}
+</div></section>
+
 
 <section id="empire"><div class="eyebrow">02 · The Portfolio — one owner, three properties</div><h2 class="disp">You're not a website. You're a media company.</h2><div class="accbody">
   <p class="lead2">Verified from your own materials (via Daniel Ginzburg, 2026): Steve Bloom operates <b>three web properties and four content franchises</b>. Today they live in silos — separate sites, separate lists, no cross-linking. Unified, they're a regional CRE media company with a marketplace attached.</p>
diff --git a/public/intake.html b/public/intake.html
index 276b7e2..9d8003d 100644
--- a/public/intake.html
+++ b/public/intake.html
@@ -94,10 +94,37 @@
       <label>Assets you have (logo, photos, video?)</label><textarea name="assets"></textarea>
     </fieldset>
 
+    <fieldset><legend>Command Center — assign to your CRM</legend>
+      <div class="hint">These are the deliverable modules in your portal. Check the ones you want us to build out and assign as tracked items in your CRM — pick one, a few, or all.</div>
+      <div class="check" style="margin-top:14px"><input type="checkbox" id="mod_all"><label for="mod_all" style="margin:0;text-transform:none;letter-spacing:0;font-weight:600">Select all</label></div>
+      <div id="modgrid" style="display:grid;grid-template-columns:1fr 1fr;gap:6px 18px;margin-top:10px"></div>
+    </fieldset>
+
     <button type="submit">Submit intake →</button>
   </form>
 
 <script>
+// Mirrors the portal hamburger (build.mjs CATS) so the client can assign any
+// deliverable module to their CRM. Keep in sync with CATS if the menu changes.
+var MODULES = [
+  ['summary','Executive Summary'],['swot','SWOT Analysis'],['reputation','Reputation & Authority'],
+  ['empire','The Empire & Sublease.com'],['competitors','Competitors'],['advertising','Where to Advertise'],
+  ['timing','Timing & Cadence'],['audit','Website Audit'],['teardown','Page-by-Page Teardown'],
+  ['social','Social Media Audit'],['suggested','Suggested New Websites'],['partners','Unlikely Partners'],
+  ['revenue','Unlikely Revenue'],['videos','Video + Brand Films'],['dealdesk','Live Deal Desk (demo)'],
+  ['roadmap','90-Day Roadmap']
+];
+(function(){
+  var grid = document.getElementById('modgrid');
+  grid.innerHTML = MODULES.map(function(m){
+    return '<label class="check" style="margin:0;text-transform:none;letter-spacing:0;font-weight:400">'
+      + '<input type="checkbox" class="modchk" value="'+m[0]+'" data-label="'+m[1]+'"> '+m[1]+'</label>';
+  }).join('');
+  document.getElementById('mod_all').addEventListener('change', function(e){
+    document.querySelectorAll('.modchk').forEach(function(c){ c.checked = e.target.checked; });
+  });
+})();
+
 document.getElementById('f').addEventListener('submit', async (e) => {
   e.preventDefault();
   const fd = new FormData(e.target);
@@ -112,6 +139,7 @@ document.getElementById('f').addEventListener('submit', async (e) => {
     goals: (o.goals||'').split('\n').map(s=>s.trim()).filter(Boolean),
     audience: o.audience, competitors: (o.competitors||'').split('\n').map(s=>s.trim()).filter(Boolean),
     aesthetic: o.aesthetic, brand_notes: o.brand_notes, assets: o.assets,
+    crm_modules: Array.prototype.map.call(document.querySelectorAll('.modchk:checked'), function(c){ return { id: c.value, label: c.getAttribute('data-label') }; }),
   };
   const r = await fetch('/api/intake', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload) });
   const j = await r.json();
diff --git a/public/portal.html b/public/portal.html
index 0df70bc..3bb4381 100644
--- a/public/portal.html
+++ b/public/portal.html
@@ -60,6 +60,11 @@ td.mo{font-family:'Cormorant Garamond',serif;color:var(--gold);font-size:18px;wi
 .cards2{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1px;background:var(--line);margin-top:34px}
 .cards2 .c{background:var(--bg);padding:32px 28px}.cards2 .c h3{font-family:'Cormorant Garamond',serif;font-size:22px;color:var(--gold);margin-bottom:10px}.cards2 .c p{color:var(--dim);font-size:16px;line-height:1.6}
 .flag{color:#e8a0a0}.good{color:#9fd6b0}
+.swotgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:1px;background:var(--line);margin-top:34px}
+.swotq{background:var(--bg);padding:28px 26px}.swotq h3{font-family:'Cormorant Garamond',serif;font-size:22px;margin-bottom:12px}
+.swotq.pos{border-top:2px solid #9fd6b0}.swotq.neg{border-top:2px solid #e8a0a0}
+.swotq.pos h3{color:#9fd6b0}.swotq.neg h3{color:#e8a0a0}
+.swotq ul{margin:0;padding-left:18px}.swotq li{color:var(--dim);font-size:15px;line-height:1.55;margin:6px 0}
 .flagbox{display:flex;gap:22px;margin-top:34px;padding:30px 32px;border:1px solid var(--gold);background:rgba(200,161,90,.07)}
 .flagbox .i{font-size:30px;color:var(--gold)}.flagbox h3{font-family:'Cormorant Garamond',serif;font-size:24px;color:var(--gold);margin-bottom:10px}.flagbox p{color:var(--ink);opacity:.92;font-size:16px;line-height:1.6}
 .vgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:20px;margin-top:20px}
@@ -114,7 +119,7 @@ footer{padding:56px clamp(24px,7vw,120px);border-top:1px solid var(--line);color
 <header class="bar"><a class="brand" href="#">REN<span>TV</span><small>Growth Strategy · 2026</small></a><button class="burger" onclick="document.body.classList.toggle('nav')" aria-label="Menu"><span></span><span></span><span></span></button></header>
 <div class="scrim" onclick="document.body.classList.remove('nav')"></div>
 <nav class="menu" onclick="if(event.target.tagName==='A')document.body.classList.remove('nav')">
-  <div class="mh">Contents</div><a href="#summary">Executive Summary</a><a href="#reputation">Reputation & Authority</a><a href="#empire">The Empire & Sublease.com</a><a href="#competitors">Competitors</a><a href="#advertising">Where to Advertise</a><a href="#timing">Timing & Cadence</a><a href="#audit">Website Audit</a><a href="#teardown">Page-by-Page Teardown</a><a href="#social">Social Media Audit</a><a href="#suggested">Suggested New Websites</a><a href="#partners">Unlikely Partners</a><a href="#revenue">Unlikely Revenue</a><a href="#videos">Video + Brand Films</a><a href="#dealdesk">Live Deal Desk (demo)</a><a href="#roadmap">90-Day Roadmap</a>
+  <div class="mh">Contents</div><a href="#summary">Executive Summary</a><a href="#swot">SWOT Analysis</a><a href="#reputation">Reputation & Authority</a><a href="#empire">The Empire & Sublease.com</a><a href="#competitors">Competitors</a><a href="#advertising">Where to Advertise</a><a href="#timing">Timing & Cadence</a><a href="#audit">Website Audit</a><a href="#teardown">Page-by-Page Teardown</a><a href="#social">Social Media Audit</a><a href="#suggested">Suggested New Websites</a><a href="#partners">Unlikely Partners</a><a href="#revenue">Unlikely Revenue</a><a href="#videos">Video + Brand Films</a><a href="#dealdesk">Live Deal Desk (demo)</a><a href="#roadmap">90-Day Roadmap</a>
   <div class="minfo"><div class="mh">RENTV.com, Inc.</div><p>5757 W. Century Blvd., #700, Los Angeles, CA 90045</p><p>Steve Bloom — Owner / Producer · (310) 213-6409</p><p>Arnie Garfinkel — CRE Talk co-host · (818) 532-7660</p><p>Allen Wolfsheimer — News/Resources · (310) 836-8238</p><p>Daniel Ginzburg — CFO, Dandeana Corp · (424) 251-8405</p><p><a href="https://rentv.com" target="_blank" rel="noopener">rentv.com ↗</a> · <a href="https://www.rentvreview.com" target="_blank" rel="noopener">The REview ↗</a> · <a href="https://www.linkedin.com/company/rentv.com-inc" target="_blank" rel="noopener">LinkedIn ↗</a></p></div>
 </nav>
 
@@ -140,6 +145,16 @@ footer{padding:56px clamp(24px,7vw,120px);border-top:1px solid var(--line);color
   <div class="cards2"><div class="c"><h3>Strengths</h3><p class="good">25-year archive &amp; authority · the State of the Market conference franchise · CRE Talk (19+ CEO episodes, sponsored) · The REview video platform · an active email operation · deep Western-U.S. focus.</p></div><div class="c"><h3>Watch-outs</h3><p class="flag">Dated pre-mobile site · social dormant & unlinked · no newsletter capture visible · no paid distribution · younger CRE pros discover news elsewhere.</p></div><div class="c"><h3>Action</h3><p>Modernize the site, turn on a daily newsletter, activate LinkedIn — convert 25 years of authority into owned audience & recurring revenue.</p></div></div>
 </div></section>
 
+<section id="swot"><div class="eyebrow">Strategic Snapshot · SWOT</div><h2 class="disp">The whole board on one screen.</h2><div class="accbody">
+  <p class="lead2">The SWOT agent runs four lenses over RENTV's profile — internal <b>Strengths</b> and <b>Weaknesses</b>, external <b>Opportunities</b> and <b>Threats</b> — so the strategy that follows has a single, honest starting picture.</p>
+  <div class="swotgrid">
+    <div class="swotq pos"><h3>Strengths</h3><ul><li>25-year brand & archive — trusted CRE authority since 1998</li><li>State of the Market conference franchise ($175 ticket, live audience)</li><li>CRE Talk — 19+ sponsored CEO episodes</li><li>The REview video platform + an active email operation</li><li>Owns sublease.com (relaunch plan in hand)</li><li>Deep, defensible Western-U.S. focus</li></ul></div>
+    <div class="swotq neg"><h3>Weaknesses</h3><ul><li>Dated, pre-mobile website</li><li>Social accounts dormant & unlinked (LinkedIn 818 followers, idle)</li><li>No visible newsletter capture — audience not owned</li><li>No paid distribution</li><li>Three properties siloed — no cross-linking, separate lists</li></ul></div>
+    <div class="swotq pos"><h3>Opportunities</h3><ul><li>Daily newsletter — convert 25 years of authority into owned audience</li><li>Relaunch sublease.com as a marketplace attached to the media brand</li><li>Recurring plans to brokers / agents / broker-dealers ($99–$900+/mo)</li><li>Package video + brand films for sponsors</li><li>Referral engines: lenders, PropTech, universities, EDC orgs</li></ul></div>
+    <div class="swotq neg"><h3>Threats</h3><ul><li>National machines out-distribute regionally (Bisnow, CoStar/LoopNet, Crexi, The Real Deal)</li><li>Younger CRE pros discover news on other platforms</li><li>Platform algorithm shifts & ad-blocking erode reach</li><li>CRE downturn softens conference & sponsor spend</li></ul></div>
+  </div>
+</div></section>
+
 
 <section id="empire"><div class="eyebrow">02 · The Portfolio — one owner, three properties</div><h2 class="disp">You're not a website. You're a media company.</h2><div class="accbody">
   <p class="lead2">Verified from your own materials (via Daniel Ginzburg, 2026): Steve Bloom operates <b>three web properties and four content franchises</b>. Today they live in silos — separate sites, separate lists, no cross-linking. Unified, they're a regional CRE media company with a marketplace attached.</p>
diff --git a/server.js b/server.js
index a671dc5..a96b86d 100644
--- a/server.js
+++ b/server.js
@@ -39,7 +39,7 @@ function gate(req, res, next) {
 // ---- data helpers ----------------------------------------------------------
 const readJSON = (f, fb) => { try { return JSON.parse(fs.readFileSync(path.join(DATA, f), 'utf8')); } catch { return fb; } };
 const writeJSON = (f, v) => fs.writeFileSync(path.join(DATA, f), JSON.stringify(v, null, 2));
-const BUCKETS = ['client', 'socials', 'suggestions', 'competitors', 'best-times', 'content-calendar', 'ads', 'directories', 'media', 'intakes'];
+const BUCKETS = ['client', 'socials', 'suggestions', 'competitors', 'best-times', 'content-calendar', 'ads', 'directories', 'media', 'intakes', 'crm'];
 
 // ---- ungated routes --------------------------------------------------------
 app.get('/', (_req, res) => res.sendFile(path.join(PUB, 'signin.html')));
@@ -47,9 +47,26 @@ app.get('/intake', (_req, res) => res.sendFile(path.join(PUB, 'intake.html')));
 app.get('/api/health', (_req, res) => res.json({ ok: true, client: 'RENTV.com', at: new Date().toISOString() }));
 app.get('/api/client', (_req, res) => res.json(readJSON('client.json', {}))); // public-safe summary for signin page
 app.post('/api/intake', (req, res) => {
+  const body = req.body || {};
+  const at = new Date().toISOString();
   const all = readJSON('intakes.json', []);
-  all.push({ received_at: new Date().toISOString(), source: 'form', intake: req.body || {} });
+  all.push({ received_at: at, source: 'form', intake: body });
   writeJSON('intakes.json', all);
+  // Fan the client's chosen command-center modules into the CRM bucket as
+  // tracked items so the admin command center picks them up immediately.
+  const mods = Array.isArray(body.crm_modules) ? body.crm_modules : [];
+  if (mods.length) {
+    const crm = readJSON('crm.json', []);
+    mods.forEach((m) => crm.push({
+      created_at: at,
+      module_id: m && m.id, module: (m && m.label) || (m && m.id),
+      business: body.business_name || null,
+      contact: body.contact_name || null,
+      email: body.contact_email || null,
+      status: 'assigned', source: 'intake',
+    }));
+    writeJSON('crm.json', crm);
+  }
   res.json({ ok: true, message: 'Thanks — your consulting intake was received.' });
 });
 

← 0d534f9 chore: final video slots, v-bump (session close)  ·  back to Consulting Rentv Com  ·  auto-save: 2026-07-17T21:48:00 (1 files) — data/crm.json acf5ed6 →