<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Fix & Flip SFH Proforma — PCG v1.9.5</title>
<style>
:root{
--bg:#f7f9fc; --card:#ffffff; --ink:#111827; --sub:#374151; --muted:#6b7280;
--border:#e5e7eb; --accent:#0ea5e9; --input:#f8fafc; --chip:#eef2f7;
}
*{box-sizing:border-box}
html,body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.5 system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif}
.wrap{max-width:1100px;margin:18px auto 64px;padding:0 12px}
header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px;flex-wrap:wrap}
.brand{display:flex;align-items:center;gap:10px}
.brand img{height:34px;border-radius:6px}
.brand svg{height:34px}
.title{font-weight:700}
.toolbar{display:flex;gap:8px;flex-wrap:wrap}
button{background:#fff;border:1px solid var(--border);border-radius:8px;padding:10px 12px;font-weight:600;cursor:pointer}
button:hover{border-color:var(--accent);box-shadow:0 0 0 3px rgba(14,165,233,.12)}
.grid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:10px}
.card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:14px;box-shadow:0 1px 2px rgba(0,0,0,.03)}
.span-6{grid-column:span 6}.span-12{grid-column:span 12}
h2{font-size:13px;margin:0 0 8px;color:var(--sub);text-transform:uppercase;letter-spacing:.08em}
label{display:block;font-size:12px;color:var(--muted);margin:6px 0 6px}
input,select{width:100%;background:var(--input);color:var(--ink);border:1px solid var(--border);border-radius:8px;padding:10px 12px;font-size:14px;outline:none}
input:focus,select:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(14,165,233,.15);background:#fff}
input::placeholder{color:#c1c9d3}
.row{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
.inline{display:flex;align-items:center;gap:8px}
.left-toggle{display:flex;align-items:center;gap:8px;margin:6px 0}
.note{font-size:12px;color:var(--muted)}
.chip{display:inline-block;background:var(--chip);border:1px solid var(--border);border-radius:999px;padding:2px 8px;font-size:12px;min-width:84px;text-align:right}
.outputs{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}
.metric{background:var(--input);border:1px solid var(--border);border-radius:12px;padding:12px}
.metric h3{margin:0 0 6px;font-size:12px;color:var(--sub);text-transform:uppercase;letter-spacing:.08em}
.metric .val{font-size:20px;font-weight:700}
.calc{font-size:12px;color:#6b7280;margin-top:6px}
.calc b{font-weight:700}
/* PDF: force exactly 2 pages */
@page{size:Letter;margin:0.5in}
.page{margin-bottom:10px}
.page.p1 .grid{grid-template-columns:repeat(12,minmax(0,1fr))}
.p1 .span-6{grid-column:span 6}
@media print{
body{background:#fff}
header .toolbar,.hide-print{display:none!important}
.wrap{margin:0 auto;padding:0}
.page{page-break-after:always}
.page:last-child{page-break-after:auto}
.p1 .grid{grid-template-columns:repeat(12,minmax(0,1fr)) !important}
.span-6,.span-12{break-inside:avoid;page-break-inside:avoid}
.card{break-inside:avoid;page-break-inside:avoid;box-shadow:none;border-color:#ddd;margin-bottom:6px}
.outputs .metric{break-inside:avoid;page-break-inside:avoid}
.metric .val{font-size:18px}
}
@media(max-width:880px){.grid{grid-template-columns:repeat(6,minmax(0,1fr))}.span-6{grid-column:span 6}.outputs{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:540px){.grid{grid-template-columns:repeat(2,minmax(0,1fr))}.span-6,.span-12{grid-column:span 2}.outputs{grid-template-columns:1fr}}
</style>
</head>
<body>
<div class="wrap">
<header>
<div class="brand">
<!-- Tiny embedded SVG logo (fallback). Use Upload Logo to swap your real logo -->
<svg viewBox="0 0 120 36" xmlns="http://www.w3.org/2000/svg" aria-label="PCG">
<rect x="0" y="0" width="120" height="36" rx="6" fill="#0ea5e9"/>
<text x="60" y="24" text-anchor="middle" font-family="Inter, system-ui, sans-serif" font-size="18" fill="#fff" font-weight="700">PCG</text>
</svg>
<div class="title">Fix & Flip SFH Proforma</div>
</div>
<div class="toolbar">
<input id="logoFile" type="file" accept=".png,.jpg,.jpeg,.webp" class="hide-print" style="display:none" />
<button id="btnLogo" class="hide-print">Upload Logo</button>
<button id="btnPDF" class="hide-print">Export → PDF</button>
<button id="btnSave" class="hide-print">Save Scenario</button>
<button id="btnLoad" class="hide-print">Load Scenario</button>
</div>
</header>
<!-- PAGE 1: all inputs compact -->
<section class="page p1">
<div class="grid">
<div class="card span-6">
<h2>Property Info</h2>
<label>Address, City, Zip</label>
<input id="address" />
<div class="row">
<div><label>Asking Price ($)</label><input data-currency id="askingPrice" /></div>
<div><label>Acquisition Price ($)</label><input data-currency id="acqPrice" /></div>
</div>
<div class="row">
<div><label>Acquisition Date (MM/DD/YYYY)</label><input id="closingDate" type="date" /></div>
<div>
<label>Monthly Holding Cost ($) <span class="note">(auto from annuals; toggle to override)</span></label>
<input id="holdingPerMo" data-currency placeholder="auto" disabled />
<div class="left-toggle">
<input type="checkbox" id="overrideHold" />
<span class="note">Override monthly holding cost</span>
</div>
</div>
</div>
<div class="row">
<div><label>Property Tax (Annual, $)</label><input id="taxAnnual" data-currency /></div>
<div><label>Insurance (Annual, $)</label><input id="insAnnual" data-currency /></div>
</div>
<div class="row">
<div><label>Misc. (Annual, $)</label><input id="miscAnnual" data-currency /></div>
<div></div>
</div>
<div class="row">
<div><label>Current Bed / Bath <span class="note">(e.g., 4/2)</span></label><input id="currentBB" /></div>
<div><label>New Bed / Bath <span class="note">(e.g., 5/3)</span></label><input id="newBB" /></div>
</div>
<div class="row">
<div><label>Garage Spaces</label><input id="garages" /></div>
<div><label>Lot Size (Sq Ft)</label><input id="lot" data-integer /></div>
</div>
<div class="row">
<div><label>Current Home Size (Sq Ft)</label><input id="curSqft" data-integer /></div>
<div><label>New Home Size (Sq Ft)</label><input id="newSqft" data-integer /></div>
</div>
</div>
<div class="card span-6">
<h2>Timeline</h2>
<div class="row">
<div><label>Permits (months)</label><input id="mPermits" data-integer /></div>
<div><label>Construction (months)</label><input id="mConst" data-integer /></div>
</div>
<div class="row">
<div><label>Marketing (months)</label><input id="mMkt" data-integer /></div>
<div><label>Total Months (auto)</label><input id="mTotal" disabled /></div>
</div>
<div class="row">
<div><label>Exit Date (auto)</label><input id="exitDateAuto" disabled /></div>
<div></div>
</div>
<h2>Construction & Soft</h2>
<div class="row">
<div><label>Construction $ / Sq Ft</label><input id="costPerSqft" data-currency /></div>
<div><label>Contingency % (default 5)</label><input id="contPct" data-percent placeholder="5" /></div>
</div>
<div class="row">
<div><label>Architect / Engineer ($)</label><input id="archFee" data-currency /></div>
<div><label>Permits ($)</label><input id="permits" data-currency /></div>
</div>
<div class="row">
<div><label>Legal Fees ($)</label><input id="legal" data-currency /></div>
<div><label>Optional Stabilized Annual NOI ($)</label><input id="noiy" data-currency /></div>
</div>
</div>
<div class="card span-6">
<h2>Financing</h2>
<div class="left-toggle">
<input type="checkbox" id="cashDeal" />
<span class="note">Cash Deal (zero out debt, fees, interest)</span>
</div>
<div class="row">
<div><label>Acquisition Interest Rate %</label><input id="acqRate" data-percent placeholder="" /></div>
<div><label>Construction Interest Rate %</label><input id="conRate" data-percent placeholder="" /></div>
</div>
<div class="row">
<div><label>Bank Fee %</label><div class="inline"><input id="bankFeePct" data-percent placeholder="1" /><span class="chip" id="bankFee$">—</span></div></div>
<div><label>Loan Broker Fee %</label><div class="inline"><input id="loanFeePct" data-percent placeholder="1" /><span class="chip" id="loanFee$">—</span></div></div>
</div>
<div class="row">
<div><label>Acquisition Closing Costs %</label><div class="inline"><input id="acqClosePct" data-percent placeholder="2" /><span class="chip" id="acqClose$">—</span></div></div>
<div><label>LTV Cap %</label><input id="ltv" data-percent placeholder="75" /></div>
</div>
<div class="row">
<div><label>LTC Cap %</label><input id="ltc" data-percent placeholder="80" /></div>
<div><label>Draw Schedule</label>
<select id="drawMode">
<option value="simple">Simple (50% avg during construction)</option>
<option value="full">Full Balance entire period</option>
</select>
</div>
</div>
</div>
<div class="card span-6">
<h2>Capital Stack</h2>
<div class="row">
<div><label>Sponsor Capital ($)</label><input id="sponCap" data-currency /></div>
<div><label>Sponsor Equity %</label><input id="sponPct" data-percent placeholder="50" /></div>
</div>
<div class="row">
<div><label>LP Capital ($)</label><input id="lpCap" data-currency /></div>
<div><label>LP Equity %</label><input id="lpPct" data-percent placeholder="50" /></div>
</div>
<div class="row">
<div><label>Total Capital Needed (auto)</label><input id="totalCapNeeded" disabled /></div>
<div><label>LP Preferred Return %</label><input id="lpPref" data-percent placeholder="0" /></div>
</div>
</div>
<div class="card span-6">
<h2>Exit</h2>
<div class="row">
<div><label>Exit Price ($)</label><input id="exitPrice" data-currency /></div>
<div><label>Sale Broker %</label><input id="saleBrokerPct" data-percent placeholder="5" /></div>
</div>
<div class="row">
<div><label>Sale Closing %</label><input id="saleClosePct" data-percent placeholder="2" /></div>
<div><label>Cost of Sale (auto)</label><input id="saleCost" disabled /></div>
</div>
</div>
</div>
</section>
<!-- PAGE 2: key outputs -->
<section class="page p2">
<div class="grid">
<div class="card span-12">
<h2>Key Outputs</h2>
<div class="outputs">
<div class="metric"><h3>% Discount</h3><div class="val" id="discount">—</div><div class="calc"><b>Calculation:</b> (Asking − Acquisition) ÷ Asking.</div></div>
<div class="metric"><h3>Loan Amount</h3><div class="val" id="loanAmt">—</div><div class="calc"><b>Calculation:</b> min(LTV×Total Cost, LTC×Total Cost) with financing factor.</div></div>
<div class="metric"><h3>Interest Reserve</h3><div class="val" id="intRes">—</div><div class="calc"><b>Calculation:</b> Loan × Rate × (weighted draw months ÷ 12).</div></div>
<div class="metric"><h3>Total Hard Costs</h3><div class="val" id="hardCosts">—</div><div class="calc"><b>Calculation:</b> (New Sq Ft × $/Sq Ft) + Contingency.</div></div>
<div class="metric"><h3>Total Soft Costs</h3><div class="val" id="softCosts">—</div><div class="calc"><b>Calculation:</b> Architect + Permits + Legal.</div></div>
<div class="metric"><h3>Total Project Cost</h3><div class="val" id="projCost">—</div><div class="calc"><b>Calculation:</b> Acquisition + Acquisition Closing + Hard + Contingency + Soft + Holding + Interest + Bank + Broker.</div></div>
<div class="metric"><h3>Profit $</h3><div class="val" id="profit">—</div><div class="calc"><b>Calculation:</b> Exit Net − Total Project Cost.</div></div>
<div class="metric"><h3>Total Time Held</h3><div class="val" id="timeHeld">—</div><div class="calc"><b>Calculation:</b> Permits + Construction + Marketing (months).</div></div>
<div class="metric"><h3>Unlevered CoC</h3><div class="val" id="uCoC">—</div><div class="calc"><b>Calculation:</b> (Exit Net − Unlevered Equity) ÷ Unlevered Equity.</div></div>
<div class="metric"><h3>Levered CoC</h3><div class="val" id="lCoC">—</div><div class="calc"><b>Calculation:</b> Profit ÷ Equity Needed.</div></div>
<div class="metric"><h3>IRR (Annualized)</h3><div class="val" id="irr">—</div><div class="calc"><b>Calculation:</b> (Equity Return ÷ Equity)^(12/Months) − 1.</div></div>
<div class="metric"><h3>Equity Multiple</h3><div class="val" id="em">—</div><div class="calc"><b>Calculation:</b> (Exit Net − Debt) ÷ Equity.</div></div>
<div class="metric"><h3>LP CoC</h3><div class="val" id="lpCoC">—</div><div class="calc"><b>Calculation:</b> LP Distribution ÷ LP Capital. (Pref accrual first; remainder by LP%.)</div></div>
<div class="metric"><h3>Break‑Even Sale</h3><div class="val" id="breakeven">—</div><div class="calc"><b>Calculation:</b> Project Cost ÷ (1 − Sale Cost %).</div></div>
<div class="metric"><h3>Margin on Cost</h3><div class="val" id="moc">—</div><div class="calc"><b>Calculation:</b> Profit ÷ Project Cost.</div></div>
<div class="metric"><h3>Yield on Cost</h3><div class="val" id="yoc">—</div><div class="calc"><b>Calculation:</b> NOI ÷ Project Cost (if NOI provided).</div></div>
</div>
<div class="note" style="margin-top:10px">
LEGAL NOTICE: This document is a pro forma analysis and is intended for informational purposes only. It does not constitute a guarantee of results, an offer to sell or solicit an offer to buy securities, or a commitment to lend. Assumptions are estimates and subject to change. Actual results may differ materially.
</div>
</div>
</div>
</section>
</div>
<script>
const $ = id => document.getElementById(id);
const raw = new Map();
const onlyDigits = s => (s||'').replace(/[^0-9.\\-]/g,'');
const parseCurrency = id => { const s = onlyDigits(raw.get(id) ?? $(id).value); const n = parseFloat(s); return isFinite(n) ? n : 0; };
const parsePercent = (id, d=0) => { const s = onlyDigits(raw.get(id) ?? $(id).value); if(!s) return d/100; const n = parseFloat(s); return isFinite(n) ? n/100 : d/100; };
const fmt$ = n => isFinite(n) ? '$'+n.toLocaleString(undefined,{maximumFractionDigits:0}) : '—';
const fmtp = n => isFinite(n) ? (n*100).toLocaleString(undefined,{maximumFractionDigits:2})+'%' : '—';
const commas = s => { if(s==null) return ''; const neg=s.startsWith('-'); s=s.replace(/[^\\d.]/g,''); const p=s.split('.'); p[0]=p[0].replace(/^0+(?=\\d)/,'').replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','); return (neg?'-':'') + (p.length>1 ? p[0]+'.'+p[1].slice(0,2) : p[0]); };
function attachFormatters(){
document.querySelectorAll('input[data-currency], input[data-percent], input[data-integer]').forEach(inp=>{
const isInt = inp.hasAttribute('data-integer');
inp.addEventListener('input', e=>{
const v = e.target.value; raw.set(inp.id, v);
const stripped = onlyDigits(v);
e.target.value = isInt ? commas(stripped.split('.')[0]) : commas(stripped);
compute();
});
inp.addEventListener('blur', compute);
});
}
function addMonths(date, months){
const d=new Date(date.getTime()), day=d.getDate(); d.setMonth(d.getMonth()+months); if(d.getDate()<day) d.setDate(0); return d;
}
const fmtDate = d => `${String(d.getMonth()+1).padStart(2,'0')}/${String(d.getDate()).padStart(2,'0')}/${d.getFullYear()}`;
function compute(){
// Inputs
const asking = parseCurrency('askingPrice');
const acq = parseCurrency('acqPrice');
const newSqf = parseCurrency('newSqft');
const cpsf = parseCurrency('costPerSqft');
const cont = parsePercent('contPct',5);
const arch = parseCurrency('archFee');
const perm = parseCurrency('permits');
const legal = parseCurrency('legal');
const taxA = parseCurrency('taxAnnual');
const insA = parseCurrency('insAnnual');
const miscA= parseCurrency('miscAnnual');
const override = $('overrideHold').checked;
const holdMoAuto = (taxA+insA+miscA)/12;
if(!override){ $('holdingPerMo').value = commas(String(Math.round(holdMoAuto))); }
$('holdingPerMo').disabled = !override;
const holdMo = override ? parseCurrency('holdingPerMo') : holdMoAuto;
const mPerm = parseCurrency('mPermits');
const mConst= parseCurrency('mConst');
const mMkt = parseCurrency('mMkt');
const months= Math.max(0, mPerm+mConst+mMkt);
$('mTotal').value = String(months);
const closeVal = $('closingDate').value;
$('exitDateAuto').value = closeVal ? fmtDate(addMonths(new Date(closeVal), months)) : '';
const cash = $('cashDeal').checked;
const acqRate = cash ? 0 : parsePercent('acqRate', 0);
const conRate = cash ? 0 : ($('conRate').value.trim()? parsePercent('conRate',0): acqRate);
const bankFeePct = cash ? 0 : parsePercent('bankFeePct',1);
const brokerFeePct= cash ? 0 : parsePercent('loanFeePct',1);
const acqClosePct = parsePercent('acqClosePct',2);
const ltv = cash ? 0 : parsePercent('ltv',75);
const ltc = cash ? 0 : parsePercent('ltc',80);
const drawMode = cash ? 'simple' : ($('drawMode').value || 'simple');
const exitPrice = parseCurrency('exitPrice');
const saleBrk = parsePercent('saleBrokerPct',5);
const saleClose = parsePercent('saleClosePct',2);
const salePct = saleBrk + saleClose;
const noiY = parseCurrency('noiy');
// Derived costs
const hard = newSqf * cpsf;
const contingency = hard * cont;
const soft = arch + perm + legal;
const holding = holdMo * months;
const acqClosing$ = acq * acqClosePct;
// Weighted draw months
const monthsPerm=mPerm, monthsConst=mConst, monthsMkt=mMkt;
const avgFactor = (drawMode==='simple') ? (monthsPerm*1 + monthsConst*0.5 + monthsMkt*1) : (monthsPerm+monthsConst+monthsMkt);
const rate = conRate || acqRate;
// Financing factor k (bank + broker + interest)
const k = bankFeePct + brokerFeePct + rate*(avgFactor/12);
const baseCosts = acq + acqClosing$ + hard + contingency + soft + holding;
const cappedLoan = m => { const d=1 - m*k; return d>0 ? (m*baseCosts)/d : 0; };
const loanLTV = cappedLoan(ltv);
const loanLTC = cappedLoan(ltc);
const loanAmt = cash ? 0 : Math.min(loanLTV, loanLTC);
const intReserve = cash ? 0 : loanAmt * rate * (avgFactor/12);
const bankFee$ = cash ? 0 : loanAmt * bankFeePct;
const brokerFee$ = cash ? 0 : loanAmt * brokerFeePct;
const saleCosts = exitPrice * salePct;
const exitNet = exitPrice - saleCosts;
const projectCost = baseCosts + intReserve + bankFee$ + brokerFee$;
const totalCapNeeded = Math.max(0, projectCost - loanAmt);
// Capital stack ($ complement)
let sDol = parseCurrency('sponCap');
let lDol = parseCurrency('lpCap');
// If user just typed in sponsor, auto-fill LP; vice versa
const active = document.activeElement ? document.activeElement.id : '';
if(active === 'sponCap'){
lDol = Math.max(0, totalCapNeeded - sDol);
$('lpCap').value = commas(String(Math.round(lDol)));
} else if(active === 'lpCap'){
sDol = Math.max(0, totalCapNeeded - lDol);
$('sponCap').value = commas(String(Math.round(sDol)));
} else {
// If LP blank, default to remainder
if(!(raw.get('lpCap')) && totalCapNeeded > 0){
lDol = Math.max(0, totalCapNeeded - sDol);
$('lpCap').value = commas(String(Math.round(lDol)));
}
}
// Percent complement (independent from $)
const sPct = parsePercent('sponPct') || 0.50;
const lPct = parsePercent('lpPct') || (1 - sPct);
if(document.activeElement && document.activeElement.id === 'sponPct'){
$('lpPct').value = ( (1 - sPct)*100 ).toFixed(2);
} else if(document.activeElement && document.activeElement.id === 'lpPct'){
$('sponPct').value = ( (1 - lPct)*100 ).toFixed(2);
}
// Returns
const equityNeeded = totalCapNeeded;
const profit = exitNet - projectCost;
const unlevEquity = baseCosts;
const uCoC = unlevEquity > 0 ? ((exitNet) - unlevEquity) / unlevEquity : NaN;
const lCoC = equityNeeded > 0 ? (profit / equityNeeded) : NaN;
const equityReturn = exitNet - loanAmt;
const em = (equityNeeded > 0) ? (equityReturn / equityNeeded) : NaN;
const irr = (equityNeeded > 0 && months > 0 && equityReturn > 0)
? (Math.pow(equityReturn / equityNeeded, 12 / months) - 1) : (equityNeeded > 0 && months > 0 && equityReturn === 0 ? -1 : NaN);
// LP Pref → LP CoC
const lpPref = parsePercent('lpPref',0);
const lpPrefAccrual = lDol * lpPref * (months/12);
const lpResidual = Math.max(0, profit - lpPrefAccrual);
const lpDistribution = Math.min(profit, lpPrefAccrual) + lpResidual * lPct;
const lpCoC = lDol > 0 ? (lpDistribution / lDol) : NaN;
const breakevenSale = (1 - salePct) > 0 ? (projectCost / (1 - salePct)) : NaN;
const moc = projectCost > 0 ? (profit / projectCost) : NaN;
const yoc = (projectCost > 0 && noiY > 0) ? (noiY / projectCost) : NaN;
const discount = (asking > 0 && acq > 0) ? ((asking - acq) / asking) : NaN;
// UI writes
$('mTotal').value = String(months);
$('saleCost').value = fmt$(saleCosts);
$('totalCapNeeded').value = fmt$(totalCapNeeded);
$('bankFee$').textContent = fmt$(bankFee$);
$('loanFee$').textContent = fmt$(brokerFee$);
$('acqClose$').textContent= fmt$(acqClosing$);
$('discount').textContent = fmtp(discount);
$('loanAmt').textContent = fmt$(loanAmt);
$('intRes').textContent = fmt$(intReserve);
$('hardCosts').textContent = fmt$(hard + contingency);
$('softCosts').textContent = fmt$(soft);
$('projCost').textContent = fmt$(projectCost);
$('profit').textContent = fmt$(profit);
$('timeHeld').textContent = months + ' mo';
$('uCoC').textContent = fmtp(uCoC);
$('lCoC').textContent = fmtp(lCoC);
$('irr').textContent = isFinite(irr) ? ((irr*100).toFixed(2)+'%') : '—';
$('em').textContent = isFinite(em) ? em.toFixed(2)+'×' : '—';
$('lpCoC').textContent = fmtp(lpCoC);
$('breakeven').textContent = fmt$(breakevenSale);
$('moc').textContent = fmtp(moc);
$('yoc').textContent = isFinite(yoc) ? fmtp(yoc) : '—';
}
function todayStr(){ const d=new Date(),p=n=>String(n).padStart(2,'0'); return `${d.getFullYear()}.${p(d.getMonth()+1)}.${p(d.getDate())}`; }
const safeAddr = ()=> ( $('address').value || 'Address').replace(/\s+/g,' ').trim();
const fileBase = ()=> `${todayStr()} ${safeAddr()} Proforma`;
function initIO(){
// Save / Load JSON
$('btnSave').addEventListener('click', ()=>{
const ids=[...document.querySelectorAll('input,select')].map(x=>x.id).filter(Boolean);
const data={}; ids.forEach(id=>data[id]=$(id).value);
const blob = new Blob([JSON.stringify(data,null,2)],{type:'application/json'});
const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download=fileBase()+'.json'; a.click();
});
$('btnLoad').addEventListener('click', ()=>{
const inp=document.createElement('input'); inp.type='file'; inp.accept='.json,application/json';
inp.onchange=e=>{ const f=e.target.files[0]; if(!f) return; const r=new FileReader();
r.onload=()=>{ try{ const data=JSON.parse(r.result); Object.keys(data).forEach(id=>{ if($(id)) $(id).value=data[id]; }); compute(); } catch{ alert('Invalid file'); } };
r.readAsText(f);
};
inp.click();
});
// Upload logo (embed as data URL so it prints in PDF)
$('btnLogo').addEventListener('click', ()=> $('logoFile').click());
$('logoFile').addEventListener('change', e=>{
const f=e.target.files[0]; if(!f) return;
const reader=new FileReader();
reader.onload=()=>{ // replace the SVG with an <img>
const brand = document.querySelector('.brand');
const old = brand.querySelector('svg');
if(old){ old.remove(); }
const img=document.createElement('img'); img.src=reader.result; img.alt='PCG Logo';
brand.insertBefore(img, brand.children[0]);
};
reader.readAsDataURL(f);
});
$('btnPDF').addEventListener('click', ()=>{
document.title = fileBase();
window.print();
});
}
window.addEventListener('DOMContentLoaded', ()=>{
attachFormatters();
['overrideHold','cashDeal','drawMode'].forEach(id=> $(id).addEventListener('change', compute));
document.querySelectorAll('input').forEach(el=> el.addEventListener('blur', compute));
compute();
initIO();
});
</script>
</body>
</html>