[object Object]

← back to Commercialrealestate

Keep DSCR Qualifier on crcp.html only; revert index.html addition

3d60c8655017fcd1b50f56ee37dd9ed14ec3e7d6 · 2026-08-06 15:02:34 -0700 · Steve

Steve prefers the crcp control-panel design (tabbed/collapsible panels, gold
accent, left-right columns) as the DSCR home. index.html restored to its
pre-DSCR state; crcp.html DSCR Qualifier + Solicitation Studio realtor_dscr
audience retained.

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

Files touched

Diff

commit 3d60c8655017fcd1b50f56ee37dd9ed14ec3e7d6
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Aug 6 15:02:34 2026 -0700

    Keep DSCR Qualifier on crcp.html only; revert index.html addition
    
    Steve prefers the crcp control-panel design (tabbed/collapsible panels, gold
    accent, left-right columns) as the DSCR home. index.html restored to its
    pre-DSCR state; crcp.html DSCR Qualifier + Solicitation Studio realtor_dscr
    audience retained.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 public/index.html | 180 ------------------------------------------------------
 1 file changed, 180 deletions(-)

diff --git a/public/index.html b/public/index.html
index 002e0ad..851c30e 100644
--- a/public/index.html
+++ b/public/index.html
@@ -460,90 +460,6 @@
 </aside>
 
 <main>
-  <!-- ===== DSCR Qualifier — no-tax-returns qualification (rents ÷ PITI) ===== -->
-  <style>
-    #dscrPanel{background:var(--card);border:1px solid var(--line);border-radius:13px;padding:16px 18px;margin-bottom:18px;}
-    #dscrPanel h2{margin:0 0 2px;font-size:14px;text-transform:uppercase;letter-spacing:.6px;color:var(--warn);}
-    #dscrPanel h2 .c{color:var(--mut);font-weight:400;text-transform:none;letter-spacing:0;font-size:12px;}
-    #dscrPanel .dsub{color:var(--mut);font-size:12.5px;margin-bottom:14px;} #dscrPanel .dsub b{color:var(--ink);}
-    #dscrPanel .dscr-grid{display:grid;grid-template-columns:1fr 1fr;gap:18px;} @media(max-width:820px){#dscrPanel .dscr-grid{grid-template-columns:1fr;}}
-    #dscrPanel .dfield{margin-bottom:10px;} #dscrPanel .dfield>label{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.5px;color:var(--mut);margin-bottom:4px;}
-    #dscrPanel .row2{display:grid;grid-template-columns:1fr 1fr;gap:10px;}
-    #dscrPanel .unitrow{display:grid;grid-template-columns:1fr 130px 32px;gap:8px;align-items:center;margin-bottom:6px;}
-    #dscrPanel .unitrow button{background:transparent;border:1px solid var(--line);color:var(--mut);border-radius:7px;cursor:pointer;font-size:15px;line-height:1;padding:7px 0;}
-    #dscrPanel .unitrow button:hover{border-color:var(--bad);color:var(--bad);}
-    #dscrPanel .addunit{background:transparent;border:1px dashed var(--line);color:var(--blue);border-radius:8px;padding:6px 11px;font-size:12px;cursor:pointer;margin-top:2px;}
-    #dscrPanel .addunit:hover{border-color:var(--blue);}
-    #dscrPanel .rentsum{font-size:12px;color:var(--mut);margin-top:7px;} #dscrPanel .rentsum b{color:var(--acc);font-variant-numeric:tabular-nums;}
-    #dscrPanel .dtog{display:inline-flex;border:1px solid var(--line);border-radius:9px;overflow:hidden;}
-    #dscrPanel .dtog button{background:var(--bg);border:0;color:var(--mut);padding:6px 12px;font-size:12px;cursor:pointer;} #dscrPanel .dtog button.on{background:rgba(210,153,34,.16);color:var(--warn);}
-    #dscrPanel .dresult{background:var(--bg);border:1px solid var(--line);border-radius:12px;padding:18px 16px;text-align:center;}
-    #dscrPanel .dresult .k{font-size:11px;text-transform:uppercase;letter-spacing:.6px;color:var(--mut);}
-    #dscrPanel .dresult .ratio{font-size:48px;font-weight:800;font-variant-numeric:tabular-nums;line-height:1;margin-top:4px;color:var(--ink);}
-    #dscrPanel .dbadge{display:inline-block;margin-top:12px;font-size:13px;font-weight:800;letter-spacing:.4px;text-transform:uppercase;padding:6px 14px;border-radius:20px;}
-    #dscrPanel .dbadge.pass{background:rgba(63,185,80,.15);color:var(--acc);border:1px solid rgba(63,185,80,.5);}
-    #dscrPanel .dbadge.marg{background:rgba(210,153,34,.15);color:var(--warn);border:1px solid rgba(210,153,34,.5);}
-    #dscrPanel .dbadge.fail{background:rgba(248,81,73,.14);color:var(--bad);border:1px solid rgba(248,81,73,.5);}
-    #dscrPanel .brk{font-size:12.5px;color:var(--mut);margin-top:14px;line-height:1.8;text-align:left;}
-    #dscrPanel .brk .r{display:flex;justify-content:space-between;gap:10px;} #dscrPanel .brk b{color:var(--ink);font-variant-numeric:tabular-nums;}
-    #dscrPanel .brk .r.hi b{color:var(--warn);}
-    #dscrPanel pre{white-space:pre-wrap;background:var(--bg);border:1px solid var(--line);border-radius:9px;padding:12px;font:12.5px/1.6 ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--ink);margin:6px 0 0;}
-    #dscrPanel .dactions{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px;} #dscrPanel .dactions button{background:var(--card);border:1px solid var(--line);color:var(--ink);border-radius:8px;padding:7px 12px;font-size:12px;cursor:pointer;} #dscrPanel .dactions button:hover{border-color:var(--blue);} #dscrPanel .dactions .prime{background:var(--warn);color:#1a1206;border-color:var(--warn);font-weight:700;}
-    #dscrPanel .dgate{font-size:11px;color:var(--warn);margin-top:8px;}
-  </style>
-  <section id="dscrPanel">
-    <h2>🧮 DSCR Qualifier <span class="c">— does the property qualify itself? no tax returns · rents ÷ PITI</span></h2>
-    <div class="dsub">A <b>DSCR</b> (debt-service-coverage-ratio) loan qualifies on the <b>property's own cash flow</b>, not the borrower's tax returns — the alternative to conventional commercial financing on small multifamily. Enter the rents and the payment: if the current or projected fair-market rents cover the PITI at the lender's ratio, it's approvable.</div>
-    <div class="dscr-grid">
-      <div>
-        <div class="dfield"><label>Property (used in Frank's note)</label><input type="text" id="dAddr" placeholder="e.g. the 95th St 6-unit"></div>
-        <div class="dfield"><label>Rent basis</label>
-          <div class="dtog" id="dRentBasis"><button type="button" class="on" data-v="current">Current rents</button><button type="button" data-v="projected">Projected fair-market</button></div>
-        </div>
-        <div class="dfield"><label>Rent roll — monthly rent per unit</label>
-          <div id="dUnits"></div>
-          <button type="button" class="addunit" id="dAddUnit">+ add unit</button>
-          <div class="rentsum">Gross monthly rents: <b id="dRentSum">—</b> &nbsp;·&nbsp; annual <b id="dRentAnn">—</b></div>
-        </div>
-        <div class="dfield"><label>PITI — the monthly payment</label>
-          <div class="dtog" id="dPitiMode"><button type="button" class="on" data-v="build">Build it</button><button type="button" data-v="direct">Enter directly</button></div>
-        </div>
-        <div id="dBuild">
-          <div class="row2">
-            <div class="dfield"><label>Loan amount</label><input type="number" id="dLoan" value="960000" min="0" step="1000"></div>
-            <div class="dfield"><label>Rate %</label><input type="number" id="dRate" value="7.25" min="0" step="0.05"></div>
-          </div>
-          <div class="row2">
-            <div class="dfield"><label>Amortization (yrs)</label><input type="number" id="dAmort" value="30" min="1" step="1"></div>
-            <div class="dfield"><label>Taxes / mo</label><input type="number" id="dTax" value="1250" min="0" step="10"></div>
-          </div>
-          <div class="row2">
-            <div class="dfield"><label>Insurance / mo</label><input type="number" id="dIns" value="450" min="0" step="10"></div>
-            <div class="dfield"><label>HOA / other / mo</label><input type="number" id="dHoa" value="0" min="0" step="10"></div>
-          </div>
-        </div>
-        <div id="dDirect" style="display:none">
-          <div class="dfield"><label>PITI / mo (all-in payment)</label><input type="number" id="dPiti" value="8200" min="0" step="10"></div>
-        </div>
-        <div class="dfield"><label>Approval DSCR (lender's line)</label><input type="number" id="dThresh" value="1.20" min="0.5" max="3" step="0.05"></div>
-      </div>
-      <div>
-        <div class="dresult">
-          <div class="k">DSCR = rents ÷ PITI</div>
-          <div class="ratio" id="dRatio">—</div>
-          <div id="dBadge" class="dbadge">—</div>
-          <div class="brk" id="dBrk"></div>
-        </div>
-        <div style="margin-top:14px">
-          <div class="k" style="font-size:11px;text-transform:uppercase;letter-spacing:.6px;color:var(--mut)">Frank's note to the referring agent</div>
-          <pre id="dNote"></pre>
-          <div class="dactions"><button type="button" class="prime" id="dCopy">📋 Copy note</button><button type="button" id="dQueue">🎬 Queue to producer</button></div>
-          <div class="dgate">Draft only — sending / producing is gated (queued for Steve, never auto-sent).</div>
-        </div>
-      </div>
-    </div>
-  </section>
-
   <!-- ===== Brokers & agents — front-page contacts (name / company / phone / email) ===== -->
   <style>
     #brokers{background:var(--card);border:1px solid var(--line);border-radius:13px;padding:14px 16px;margin-bottom:18px;}
@@ -735,102 +651,6 @@
 <div id="cmpov"><div id="cmpmodal"><button class="cmpx" aria-label="Close" onclick="document.getElementById('cmpov').classList.remove('on')">✕</button><h3 style="margin:0 0 10px">⚖ Compare shortlist</h3><div id="cmpbody"></div></div></div>
 <script src="finance.js"></script>
 <script src="deal-score.js"></script>
-<script>
-// ── DSCR Qualifier — rents ÷ PITI, no-tax-returns qualification + Frank's referral note ──
-// Self-contained (own helpers, no page-global collisions). Reuses CREFin.debtService (finance.js)
-// so the qualifier and the listing ranker share one amortization — they can never disagree.
-(function(){
-  const panel=document.getElementById('dscrPanel'); if(!panel) return;
-  let basis='current', pitiMode='build';
-  function monthlyPI(loan, ratePct, amortYears){
-    if(!(loan>0)) return 0;
-    if(window.CREFin && CREFin.debtService) return CREFin.debtService(loan, ratePct, amortYears)/12;
-    const r=ratePct/100/12, n=amortYears*12;
-    return r===0 ? loan/n : loan*(r*Math.pow(1+r,n))/(Math.pow(1+r,n)-1);
-  }
-  const num=id=>{ const el=document.getElementById(id); const v=el?parseFloat(el.value):NaN; return isFinite(v)?v:0; };
-  const usd=n=>'$'+Math.round(n).toLocaleString();
-  const DEF=[1850,1850,1850,1850,1850,1850]; // seeds the six-unit story
-  function unitRows(){ return Array.from(document.querySelectorAll('#dUnits .unitrow')); }
-  function grossMonthly(){ return unitRows().reduce((s,r)=>{ const v=parseFloat(r.querySelector('.urent').value); return s+(isFinite(v)?v:0); },0); }
-  function addUnit(rent){
-    const wrap=document.getElementById('dUnits'); const i=unitRows().length+1;
-    const row=document.createElement('div'); row.className='unitrow';
-    row.innerHTML=`<input type="text" class="ulabel" value="Unit ${i}" aria-label="Unit label">`+
-      `<input type="number" class="urent" value="${rent!=null?rent:1800}" min="0" step="25" aria-label="Monthly rent">`+
-      `<button type="button" class="udel" title="Remove unit">×</button>`;
-    row.querySelector('.urent').addEventListener('input',recompute);
-    row.querySelector('.udel').addEventListener('click',()=>{ if(unitRows().length>1){ row.remove(); recompute(); } });
-    wrap.appendChild(row);
-  }
-  function verdict(dscr, thresh){
-    if(dscr==null||!isFinite(dscr)) return {cls:'',txt:'—'};
-    if(dscr>=thresh) return {cls:'pass',txt:'✓ Approves — no tax returns'};
-    if(dscr>=1.0)    return {cls:'marg',txt:'⚠ Marginal — covers, below the line'};
-    return {cls:'fail',txt:'✕ Short — rents don’t cover PITI'};
-  }
-  function buildNote(g){
-    const addr=(document.getElementById('dAddr').value||'').trim()||'that property';
-    const units=unitRows().length, basisWord=basis==='projected'?'projected fair-market':'current';
-    let line, close;
-    if(g.dscr==null){ line='the payment nets to zero'; close='Send me the rent roll and I’ll pencil it.'; }
-    else if(g.dscr>=g.thresh){ line=`a ${g.dscr.toFixed(2)} DSCR — clears the ${g.thresh.toFixed(2)} line with ${usd(g.surplus)}/mo to spare`; close='It qualifies as-is — happy to send terms and get an app started whenever your seller’s ready.'; }
-    else if(g.dscr>=1.0){ line=`a ${g.dscr.toFixed(2)} DSCR — the rents cover the payment but it’s under a typical ${g.thresh.toFixed(2)} approval line`; close=`We can still get there: about ${usd(g.rentsNeeded-g.gross)}/mo more in rent, a bit more down, or a lender at ${g.dscr.toFixed(2)} min gets it done. Want me to run those?`; }
-    else { line=`a ${g.dscr.toFixed(2)} DSCR — the rents fall about ${usd(g.shortfall)}/mo short of the payment`; close=`It doesn’t pencil at asking on rents alone — projected fair-market rents or more down would move it. Want me to model it?`; }
-    return `Hey — thanks for the list. I called the agent on ${addr} and ran DSCR numbers on it as an alternative to commercial financing on that ${units}-unit.\n\n`+
-      `The nice part: it’s a DSCR loan, so no tax returns — it qualifies on the building’s own rents.\n\n`+
-      `  • ${basisWord[0].toUpperCase()+basisWord.slice(1)} rents:  ${usd(g.gross)}/mo  (${usd(g.gross*12)}/yr)\n`+
-      `  • Full payment (PITI):  ${usd(g.piti)}/mo\n`+
-      `  • DSCR:  ${g.dscr==null?'—':g.dscr.toFixed(2)}  →  ${line}\n\n`+
-      `${close}\n\n`+
-      `Happy to run a same-day scenario on any of the others on your list.\n— Frank · your commercial-lending desk`;
-  }
-  function recompute(){
-    const gross=grossMonthly();
-    document.getElementById('dRentSum').textContent=usd(gross);
-    document.getElementById('dRentAnn').textContent=usd(gross*12);
-    let piti;
-    if(pitiMode==='build'){ piti = monthlyPI(num('dLoan'),num('dRate'),num('dAmort')||30) + num('dTax') + num('dIns') + num('dHoa'); }
-    else { piti = num('dPiti'); }
-    const thresh=num('dThresh')||1.20;
-    const dscr = piti>0 ? gross/piti : null;
-    const g={ gross, piti, thresh, dscr, surplus: gross-piti, shortfall: piti-gross,
-      maxPiti: thresh>0 ? gross/thresh : 0, rentsNeeded: piti*thresh };
-    document.getElementById('dRatio').textContent = dscr==null?'—':dscr.toFixed(2);
-    const v=verdict(dscr,thresh), badge=document.getElementById('dBadge');
-    badge.className='dbadge '+v.cls; badge.textContent=v.txt;
-    document.getElementById('dRatio').style.color = v.cls==='pass'?'var(--acc)':v.cls==='marg'?'var(--warn)':v.cls==='fail'?'var(--bad)':'var(--ink)';
-    const gap = dscr==null?'':(dscr>=thresh
-      ? `<div class="r"><span>Monthly surplus over payment</span><b style="color:var(--acc)">${usd(g.surplus)}</b></div>`
-      : `<div class="r"><span>Rents needed to hit ${thresh.toFixed(2)}</span><b>${usd(g.rentsNeeded)}/mo</b></div>`+
-        `<div class="r"><span>${gross>=piti?'Cushion above payment':'Monthly shortfall'}</span><b style="color:${gross>=piti?'var(--acc)':'var(--bad)'}">${usd(Math.abs(g.surplus))}</b></div>`);
-    document.getElementById('dBrk').innerHTML =
-      `<div class="r"><span>Gross monthly rents</span><b>${usd(gross)}</b></div>`+
-      `<div class="r"><span>PITI (payment)</span><b>${usd(piti)}</b></div>`+
-      `<div class="r hi"><span>Max payment these rents support @ ${thresh.toFixed(2)}</span><b>${usd(g.maxPiti)}/mo</b></div>`+
-      gap;
-    document.getElementById('dNote').textContent = buildNote(g);
-    return g;
-  }
-  document.querySelectorAll('#dRentBasis button').forEach(b=>b.onclick=()=>{ basis=b.dataset.v; b.parentNode.querySelectorAll('button').forEach(x=>x.classList.toggle('on',x===b)); recompute(); });
-  document.querySelectorAll('#dPitiMode button').forEach(b=>b.onclick=()=>{ pitiMode=b.dataset.v; b.parentNode.querySelectorAll('button').forEach(x=>x.classList.toggle('on',x===b)); document.getElementById('dBuild').style.display=pitiMode==='build'?'':'none'; document.getElementById('dDirect').style.display=pitiMode==='direct'?'':'none'; recompute(); });
-  ['dLoan','dRate','dAmort','dTax','dIns','dHoa','dPiti','dThresh','dAddr'].forEach(id=>{ const el=document.getElementById(id); if(el) el.addEventListener('input',recompute); });
-  document.getElementById('dAddUnit').onclick=()=>{ addUnit(1800); recompute(); };
-  document.getElementById('dCopy').onclick=()=>{ const t=document.getElementById('dNote').textContent; navigator.clipboard&&navigator.clipboard.writeText(t); const b=document.getElementById('dCopy'); b.textContent='✓ Copied'; setTimeout(()=>b.textContent='📋 Copy note',1200); };
-  document.getElementById('dQueue').onclick=()=>{
-    const b=document.getElementById('dQueue'); b.disabled=true; b.textContent='queuing…';
-    const g=recompute(), addr=(document.getElementById('dAddr').value||'').trim()||'DSCR scenario';
-    fetch('/api/solicitation/queue',{method:'POST',headers:{'content-type':'application/json'},
-      body:JSON.stringify({tone:'warm',type:'dscr-note',audience:'realtor_dscr',
-        subject:`DSCR note · ${addr} · ${g.dscr==null?'—':g.dscr.toFixed(2)}`,body:document.getElementById('dNote').textContent})})
-      .then(r=>r.json()).then(d=>{ b.textContent=d&&d.ok?('✓ Queued #'+(d.count||'')+' — gated'):'✓ Queued (draft)'; })
-      .catch(()=>{ b.textContent='✓ Queued (local draft)'; })
-      .finally(()=>{ setTimeout(()=>{ b.disabled=false; b.textContent='🎬 Queue to producer'; },2500); });
-  };
-  DEF.forEach(r=>addUnit(r));
-  recompute();
-})();
-</script>
 <!-- href-to-deeper-data primitives (shared atoms). CRCP drives drilling through its
      own native Set-based facet model (data-firmfilter / data-typefilter / data-cityfilter
      → F.firms/F.types/F.cityQuery → render()→stateToURL()); this shared module is loaded

← 750c9b8 Add DSCR Qualifier to the initial page (index.html) + Frank'  ·  back to Commercialrealestate  ·  docs: LA current-sales sourcing memo (ULA/deeds, public-reco e8c77da →