<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Beach City Painting — Estimator</title>
<style>
:root{
--bg:#0b1220; --card:#121a2b; --muted:#8aa0c2; --ink:#f2f6ff; --accent:#3ea7ff; --ok:#27c384; --warn:#ffb020;
--br:16px;
}
*{box-sizing:border-box}
html,body{margin:0;background:var(--bg);color:var(--ink);font-family:ui-sans-serif,system-ui,Segoe UI,Roboto,Helvetica,Arial;}
.wrap{max-width:1100px;margin:32px auto;padding:0 16px}
h1{font-size:28px;margin:0 0 10px}
.sub{color:var(--muted);margin:0 0 24px;font-size:14px}
.grid{display:grid;gap:16px}
@media(min-width:900px){.grid{grid-template-columns:1fr 1fr}}
.card{background:var(--card);border-radius:var(--br);padding:16px;border:1px solid #1f2b45}
.card h2{margin:0 0 10px;font-size:18px}
.row{display:grid;grid-template-columns:1fr 110px;gap:10px;margin:8px 0}
.row > label{display:flex;align-items:center;gap:8px;color:var(--muted);font-size:13px}
input[type="number"], input[type="text"], select{width:100%;padding:10px 12px;border-radius:10px;border:1px solid #2a3a5d;background:#0e1526;color:var(--ink)}
input[type="checkbox"]{transform:scale(1.1)}
.stack{display:grid;gap:8px}
.cols{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
.cols4{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}
.note{color:var(--muted);font-size:12px}
.totals{display:grid;gap:12px}
.pill{display:flex;justify-content:space-between;padding:12px;border-radius:12px;background:#0e1526;border:1px solid #1d2840}
.pill strong{font-weight:700}
.btnbar{display:flex;gap:10px;flex-wrap:wrap}
button{padding:10px 14px;border-radius:10px;border:1px solid #25507d;background:#10223a;color:#e7f1ff;cursor:pointer}
button.primary{background:linear-gradient(180deg,#2d8fff,#1f7be0);border-color:#1f7be0}
button.ghost{background:transparent}
table{width:100%;border-collapse:collapse;border:1px solid #1f2b45;border-radius:12px;overflow:hidden}
th,td{padding:10px 12px;border-bottom:1px solid #1f2b45;text-align:right}
th:first-child, td:first-child{text-align:left}
tfoot td{font-weight:700}
.tag{display:inline-block;padding:2px 8px;border-radius:999px;background:#0e1526;border:1px solid #1d2840;color:#c6d6f3;font-size:12px}
.flex{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.hr{height:1px;background:#1f2b45;margin:14px 0}
a.small{color:#a9c8ff;text-decoration:none;border-bottom:1px dotted #2a63b0}
</style>
</head>
<body>
<div class="wrap">
<h1>Project Estimator <span class="tag">Beach City Painting</span></h1>
<p class="sub">Embeddable calculator based on your spreadsheet logic (primer + 2 coats model, doors/trim, garage, exterior). Values persist locally.</p>
<div class="grid">
<section class="card">
<h2>Project & Global Settings</h2>
<div class="row"><label>Project Name</label><input id="projectName" type="text" placeholder="e.g., 623 9th St — Interior"/></div>
<div class="cols">
<div class="stack">
<label class="note">Markup (%)</label>
<input id="markupPct" type="number" step="1" value="0"/>
</div>
<div class="stack">
<label class="note">Sales Tax (%)</label>
<input id="taxPct" type="number" step="0.01" value="0"/>
</div>
<div class="stack">
<label class="note">Currency</label>
<select id="currency"><option value="$">$</option><option value="€">€</option><option value="£">£</option></select>
</div>
</div>
<div class="hr"></div>
<div class="btnbar">
<button class="primary" id="calcBtn">Calculate</button>
<button id="resetBtn" class="ghost">Reset</button>
<button id="exportBtn">Export JSON</button>
</div>
</section>
<section class="card">
<h2>Pricing (per unit)</h2>
<p class="note">You can adjust these at any time. Interior walls/ceilings are per square foot; doors & trim are per piece or linear foot.</p>
<div class="cols4">
<div class="stack">
<label class="note">Walls — Primer</label>
<input id="rateWallsPrimer" type="number" step="0.01" value="0.00"/>
</div>
<div class="stack">
<label class="note">Walls — Coat 1</label>
<input id="rateWallsC1" type="number" step="0.01" value="0.75"/>
</div>
<div class="stack">
<label class="note">Walls — Coat 2</label>
<input id="rateWallsC2" type="number" step="0.01" value="0.65"/>
</div>
<div class="stack">
<label class="note">Wall Linear Factor (per sqft → LF)</label>
<input id="factorWallsLF" type="number" step="0.01" value="0.25"/>
</div>
</div>
<div class="cols4" style="margin-top:8px">
<div class="stack">
<label class="note">Ceilings — Primer</label>
<input id="rateCeilPrimer" type="number" step="0.01" value="0.00"/>
</div>
<div class="stack">
<label class="note">Ceilings — Coat 1</label>
<input id="rateCeilC1" type="number" step="0.01" value="0.75"/>
</div>
<div class="stack">
<label class="note">Ceilings — Coat 2</label>
<input id="rateCeilC2" type="number" step="0.01" value="0.50"/>
</div>
<div class="stack">
<label class="note">Baseboards — per LF (per coat)</label>
<input id="rateBaseLF" type="number" step="0.01" value="0.50"/>
</div>
</div>
<div class="cols4" style="margin-top:8px">
<div class="stack">
<label class="note">Interior Door (each) — Primer</label>
<input id="rateDoorIntP" type="number" step="0.01" value="0.00"/>
</div>
<div class="stack">
<label class="note">Interior Door — Coat 1</label>
<input id="rateDoorIntC1" type="number" step="0.01" value="50.00"/>
</div>
<div class="stack">
<label class="note">Interior Door — Coat 2</label>
<input id="rateDoorIntC2" type="number" step="0.01" value="50.00"/>
</div>
<div class="stack">
<label class="note">Baseboard LF Factor (sqft → LF)</label>
<input id="factorBaseLF" type="number" step="0.01" value="0.21"/>
</div>
</div>
<div class="cols4" style="margin-top:8px">
<div class="stack">
<label class="note">French Door — per coat (each)</label>
<input id="rateDoorFrench" type="number" step="0.01" value="60.00"/>
</div>
<div class="stack">
<label class="note">Exterior Door — per coat (each)</label>
<input id="rateDoorExt" type="number" step="0.01" value="60.00"/>
</div>
<div class="stack">
<label class="note">Casings — per LF (per coat)</label>
<input id="rateCasingLF" type="number" step="0.01" value="0.50"/>
</div>
<div class="stack">
<label class="note">Casing LF / interior door</label>
<input id="casingLFPerDoor" type="number" step="1" value="34"/>
</div>
</div>
<div class="cols4" style="margin-top:8px">
<div class="stack">
<label class="note">Garage — Primer (per sqft)</label>
<input id="rateGarageP" type="number" step="0.01" value="0.35"/>
</div>
<div class="stack">
<label class="note">Garage — Coat 1 (per sqft)</label>
<input id="rateGarageC1" type="number" step="0.01" value="0.45"/>
</div>
<div class="stack">
<label class="note">Garage — Coat 2 (per sqft)</label>
<input id="rateGarageC2" type="number" step="0.01" value="0.45"/>
</div>
<div class="stack">
<label class="note">Garage LF Factor (sqft → LF)</label>
<input id="factorGarageLF" type="number" step="0.01" value="0.25"/>
</div>
</div>
<div class="cols4" style="margin-top:8px">
<div class="stack">
<label class="note">Exterior — Primer (per sqft)</label>
<input id="rateExtP" type="number" step="0.01" value="0.50"/>
</div>
<div class="stack">
<label class="note">Exterior — Coat 1 (per sqft)</label>
<input id="rateExtC1" type="number" step="0.01" value="0.90"/>
</div>
<div class="stack">
<label class="note">Exterior — Coat 2 (per sqft)</label>
<input id="rateExtC2" type="number" step="0.01" value="0.90"/>
</div>
<div class="stack">
<label class="note">Currency Decimals</label>
<input id="decimals" type="number" step="1" value="2"/>
</div>
</div>
</section>
</div>
<div class="grid" style="margin-top:16px">
<section class="card">
<h2>Interior — Walls</h2>
<div class="cols">
<div class="stack">
<label class="note">Listed Sqft</label>
<input id="wallsSqft" type="number" step="1" value="1867"/>
</div>
<div class="stack">
<label class="note">Height (ft)</label>
<select id="wallsHeight">
<option>8</option>
<option selected>10</option>
<option>12</option>
<option value="custom">Custom…</option>
</select>
<input id="wallsHeightCustom" type="number" step="0.1" placeholder="Enter custom height" style="display:none" />
</div>
</div>
<div class="cols4" style="margin-top:8px">
<div class="stack">
<label class="note">Use Primer?</label>
<select id="wallsUsePrimer"><option value="0">No</option><option value="1">Yes</option></select>
</div>
<div class="stack"><label class="note">Coat 1?</label><select id="wallsUseC1"><option value="1">Yes</option><option value="0">No</option></select></div>
<div class="stack"><label class="note">Coat 2?</label><select id="wallsUseC2"><option value="1">Yes</option><option value="0">No</option></select></div>
<div class="stack"><label class="note">Condition Factor</label><input id="wallsCond" type="number" step="0.01" value="1.00"/></div>
</div>
<p class="note">Formula: <em>Linear Ft = Listed Sqft × Wall Linear Factor</em>; <em>Total Sqft = Linear Ft × Height</em>.</p>
<div id="wallsOut" class="note"></div>
</section>
<section class="card">
<h2>Interior — Ceilings</h2>
<div class="cols">
<div class="stack">
<label class="note">Ceiling Sqft</label>
<input id="ceilSqft" type="number" step="1" value="1867"/>
</div>
<div class="stack">
<label class="note">Use Primer?</label>
<select id="ceilUsePrimer"><option value="0">No</option><option value="1">Yes</option></select>
</div>
</div>
<div class="cols">
<div class="stack"><label class="note">Coat 1?</label><select id="ceilUseC1"><option value="1">Yes</option><option value="0">No</option></select></div>
<div class="stack"><label class="note">Coat 2?</label><select id="ceilUseC2"><option value="1">Yes</option><option value="0">No</option></select></div>
</div>
<div id="ceilOut" class="note"></div>
</section>
</div>
<div class="grid" style="margin-top:16px">
<section class="card">
<h2>Doors & Trim</h2>
<div class="cols4">
<div class="stack"><label class="note">Interior Doors</label><input id="nDoorInt" type="number" step="1" value="12"/></div>
<div class="stack"><label class="note">French Doors</label><input id="nDoorFrench" type="number" step="1" value="0"/></div>
<div class="stack"><label class="note">Exterior Doors</label><input id="nDoorExt" type="number" step="1" value="0"/></div>
<div class="stack"><label class="note">Prime Doors?</label><select id="doorsPrime"><option value="0">No</option><option value="1">Yes</option></select></div>
</div>
<div class="hr"></div>
<div class="cols4">
<div class="stack"><label class="note">House Sqft (for baseboard LF)</label><input id="houseSqftForBase" type="number" step="1" value="2250"/></div>
<div class="stack"><label class="note">Baseboard LF (override)</label><input id="baseLFOverride" type="number" step="1" placeholder="optional"/></div>
<div class="stack"><label class="note">Casing Doors (count)</label><input id="casingDoors" type="number" step="1" value="14"/></div>
<div class="stack"><label class="note">Include Install Costs?</label><select id="includeInstall"><option value="0">No</option><option value="1">Yes</option></select></div>
</div>
<div id="trimOut" class="note"></div>
</section>
<section class="card">
<h2>Garage</h2>
<div class="cols">
<div class="stack"><label class="note">Garage Sqft (listed)</label><input id="garageSqft" type="number" step="1" value="425"/></div>
<div class="stack"><label class="note">Height (ft)</label>
<select id="garageHeight"><option>8</option><option selected>10</option><option>12</option></select>
</div>
</div>
<div class="cols">
<div class="stack"><label class="note">Use Primer?</label><select id="garageUseP"><option value="1">Yes</option><option value="0">No</option></select></div>
<div class="stack"><label class="note">Coat 1?</label><select id="garageUseC1"><option value="1">Yes</option><option value="0">No</option></select></div>
<div class="stack"><label class="note">Coat 2?</label><select id="garageUseC2"><option value="1">Yes</option><option value="0">No</option></select></div>
</div>
<div id="garageOut" class="note"></div>
</section>
</div>
<section class="card" style="margin-top:16px">
<h2>Exterior</h2>
<div class="cols">
<div class="stack"><label class="note">Perimeter (LF)</label><input id="extPerim" type="number" step="1" value="240"/></div>
<div class="stack"><label class="note">Height (ft)</label><input id="extHeight" type="number" step="0.1" value="10"/></div>
<div class="stack"><label class="note">Second Floor Perimeter (LF)</label><input id="extPerim2" type="number" step="1" value="160"/></div>
<div class="stack"><label class="note">Second Floor Height (ft)</label><input id="extHeight2" type="number" step="0.1" value="9"/></div>
</div>
<div class="cols">
<div class="stack"><label class="note">Use Primer?</label><select id="extUseP"><option value="1">Yes</option><option value="0">No</option></select></div>
<div class="stack"><label class="note">Coat 1?</label><select id="extUseC1"><option value="1">Yes</option><option value="0">No</option></select></div>
<div class="stack"><label class="note">Coat 2?</label><select id="extUseC2"><option value="1">Yes</option><option value="0">No</option></select></div>
</div>
<div id="extOut" class="note"></div>
</section>
<section class="card" style="margin-top:16px">
<h2>Estimate Summary</h2>
<div class="totals" id="summary">
<div class="pill"><span>Interior Walls</span><strong id="sumWalls">$0.00</strong></div>
<div class="pill"><span>Ceilings</span><strong id="sumCeil">$0.00</strong></div>
<div class="pill"><span>Doors & Trim</span><strong id="sumTrim">$0.00</strong></div>
<div class="pill"><span>Garage</span><strong id="sumGarage">$0.00</strong></div>
<div class="pill"><span>Exterior</span><strong id="sumExt">$0.00</strong></div>
<div class="pill" style="border-color:#2b3d63"><span>Subtotal</span><strong id="sumSub">$0.00</strong></div>
<div class="pill"><span>Markup</span><strong id="sumMarkup">$0.00</strong></div>
<div class="pill"><span>Tax</span><strong id="sumTax">$0.00</strong></div>
<div class="pill" style="background:#0b1b2e;border-color:#39527f"><span>Total</span><strong id="sumTotal">$0.00</strong></div>
</div>
<div style="margin-top:14px">
<table id="detailTable">
<thead>
<tr><th>Line</th><th>Qty</th><th>Unit</th><th>Rate</th><th>Cost</th></tr>
</thead>
<tbody></tbody>
<tfoot>
<tr><td colspan="4">Subtotal</td><td id="tblSub" style="font-weight:700;text-align:right">$0.00</td></tr>
</tfoot>
</table>
</div>
</section>
<p class="note" style="margin-top:16px">Embed on Squarespace: add a <em>Code</em> block, paste this file's HTML contents, or host the HTML and iframe it. All values persist in the browser via localStorage (no backend).</p>
</div>
<script>
(function(){
const $ = (id)=>document.getElementById(id);
const ids = [
'projectName','markupPct','taxPct','currency','decimals',
'rateWallsPrimer','rateWallsC1','rateWallsC2','factorWallsLF',
'rateCeilPrimer','rateCeilC1','rateCeilC2','rateBaseLF','factorBaseLF',
'rateDoorIntP','rateDoorIntC1','rateDoorIntC2','rateDoorFrench','rateDoorExt','rateCasingLF','casingLFPerDoor',
'rateGarageP','rateGarageC1','rateGarageC2','factorGarageLF',
'rateExtP','rateExtC1','rateExtC2',
'wallsSqft','wallsHeight','wallsHeightCustom','wallsUsePrimer','wallsUseC1','wallsUseC2','wallsCond',
'ceilSqft','ceilUsePrimer','ceilUseC1','ceilUseC2',
'nDoorInt','nDoorFrench','nDoorExt','doorsPrime',
'houseSqftForBase','baseLFOverride','casingDoors','includeInstall',
'garageSqft','garageHeight','garageUseP','garageUseC1','garageUseC2',
'extPerim','extHeight','extPerim2','extHeight2','extUseP','extUseC1','extUseC2'
];
const storeKey = 'bcp-estimator-v1';
function fmt(n){ const dec = +$('decimals').value||2; const cur=$('currency').value; return cur + (Number(n).toFixed(dec)); }
function n(v){ return Number(v)||0; }
function save(){
const data = {};
ids.forEach(id=>{ const el=$(id); if(!el) return; data[id] = (el.type === 'checkbox') ? el.checked : el.value; });
localStorage.setItem(storeKey, JSON.stringify(data));
}
function load(){
const raw = localStorage.getItem(storeKey); if(!raw) return;
try{
const data = JSON.parse(raw);
ids.forEach(id=>{ if(data[id]!==undefined){ const el=$(id); if(!el) return; if(el.type==='checkbox'){ el.checked = !!data[id]; } else { el.value = data[id]; } }});
}catch(e){ console.warn('load failed', e); }
}
function heightValue(selId, customId){
const sel = $(selId).value; if(sel==='custom'){ return n($(customId).value||0); } return n(sel);
}
function pushRow(rows, label, qty, unit, rate, coats=1){
const r = n(rate) * n(qty) * n(coats);
if(r<=0) return 0;
rows.push({label, qty, unit, rate, cost:r});
return r;
}
function calc(){
const rows=[];
// Globals
const markupPct = n($('markupPct').value)/100;
const taxPct = n($('taxPct').value)/100;
// Walls
const wallsSqft = n($('wallsSqft').value);
const wallsLF = wallsSqft * n($('factorWallsLF').value);
const wallsHeight = heightValue('wallsHeight','wallsHeightCustom');
const wallsTotalSqft = wallsLF * wallsHeight * n($('wallsCond').value);
const wallsRates = {
p: n($('rateWallsPrimer').value), c1: n($('rateWallsC1').value), c2: n($('rateWallsC2').value)
};
const useWP = $('wallsUsePrimer').value==='1', useWC1 = $('wallsUseC1').value==='1', useWC2 = $('wallsUseC2').value==='1';
let sumWalls = 0;
if(useWP) sumWalls += pushRow(rows, 'Interior Walls — Primer', wallsTotalSqft, 'sqft', wallsRates.p);
if(useWC1) sumWalls += pushRow(rows, 'Interior Walls — Coat 1', wallsTotalSqft, 'sqft', wallsRates.c1);
if(useWC2) sumWalls += pushRow(rows, 'Interior Walls — Coat 2', wallsTotalSqft, 'sqft', wallsRates.c2);
$('wallsOut').innerText = `Linear Ft: ${wallsLF.toFixed(2)} | Total Sqft: ${wallsTotalSqft.toFixed(2)}`;
// Ceilings
const ceilSqft = n($('ceilSqft').value);
const ceilRates = { p:n($('rateCeilPrimer').value), c1:n($('rateCeilC1').value), c2:n($('rateCeilC2').value) };
const useCP = $('ceilUsePrimer').value==='1', useCC1=$('ceilUseC1').value==='1', useCC2=$('ceilUseC2').value==='1';
let sumCeil=0;
if(useCP) sumCeil += pushRow(rows, 'Ceilings — Primer', ceilSqft, 'sqft', ceilRates.p);
if(useCC1) sumCeil += pushRow(rows, 'Ceilings — Coat 1', ceilSqft, 'sqft', ceilRates.c1);
if(useCC2) sumCeil += pushRow(rows, 'Ceilings — Coat 2', ceilSqft, 'sqft', ceilRates.c2);
$('ceilOut').innerText = `Ceiling Sqft: ${ceilSqft.toFixed(2)}`;
// Doors & Trim
const nDoorInt = n($('nDoorInt').value), nDoorFrench = n($('nDoorFrench').value), nDoorExt = n($('nDoorExt').value);
const doorsPrime = $('doorsPrime').value==='1';
const dRates = { intP:n($('rateDoorIntP').value), intC1:n($('rateDoorIntC1').value), intC2:n($('rateDoorIntC2').value), french:n($('rateDoorFrench').value), ext:n($('rateDoorExt').value) };
let sumTrim=0;
if(doorsPrime && dRates.intP>0) sumTrim += pushRow(rows,'Interior Doors — Primer', nDoorInt, 'each', dRates.intP);
sumTrim += pushRow(rows,'Interior Doors — Coat 1', nDoorInt, 'each', dRates.intC1);
sumTrim += pushRow(rows,'Interior Doors — Coat 2', nDoorInt, 'each', dRates.intC2);
if(nDoorFrench>0){
sumTrim += pushRow(rows,'French Doors — Coat 1', nDoorFrench, 'each', dRates.french);
sumTrim += pushRow(rows,'French Doors — Coat 2', nDoorFrench, 'each', dRates.french);
}
if(nDoorExt>0){
sumTrim += pushRow(rows,'Exterior Doors — Coat 1', nDoorExt, 'each', dRates.ext);
sumTrim += pushRow(rows,'Exterior Doors — Coat 2', nDoorExt, 'each', dRates.ext);
}
// Baseboards
const baseLF = $('baseLFOverride').value ? n($('baseLFOverride').value) : (n($('houseSqftForBase').value) * n($('factorBaseLF').value));
const baseRate = n($('rateBaseLF').value);
// 2 coats by default (matches spreadsheet examples: $1.00,$0.50,$0.50 → if primer is $1, you could enable primer via doorsPrime or adjust rateBaseLF accordingly)
sumTrim += pushRow(rows,'Baseboards (prep/paint) — Coat 1', baseLF, 'LF', baseRate);
sumTrim += pushRow(rows,'Baseboards — Coat 2', baseLF, 'LF', baseRate);
// Casings
const casingDoors = n($('casingDoors').value);
const casingLF = casingDoors * n($('casingLFPerDoor').value);
const casingRate = n($('rateCasingLF').value);
sumTrim += pushRow(rows,'Casings (prep/paint) — Coat 1', casingLF, 'LF', casingRate);
sumTrim += pushRow(rows,'Casings — Coat 2', casingLF, 'LF', casingRate);
const includeInstall = $('includeInstall').value==='1';
if(includeInstall){
// Example install placeholder using spreadsheet sample ($4,116.74 on ~392 LF → ~$10.50/LF). Adjust as needed.
const approxBaseInstallRate = 10.50; // editable here if you want a fixed install adder
const installCost = baseLF * approxBaseInstallRate;
sumTrim += pushRow(rows,'Baseboard Install (complete)', baseLF, 'LF', approxBaseInstallRate);
}
$('trimOut').innerText = `Interior Doors: ${nDoorInt} | Baseboard LF: ${baseLF.toFixed(2)} | Casing LF: ${casingLF.toFixed(2)}`;
// Garage
const gSqft = n($('garageSqft').value);
const gLF = gSqft * n($('factorGarageLF').value);
const gHeight = n($('garageHeight').value);
const gArea = gLF * gHeight;
const gRates = { p:n($('rateGarageP').value), c1:n($('rateGarageC1').value), c2:n($('rateGarageC2').value) };
const useGP = $('garageUseP').value==='1', useGC1 = $('garageUseC1').value==='1', useGC2 = $('garageUseC2').value==='1';
let sumGarage=0;
if(useGP) sumGarage += pushRow(rows,'Garage — Primer', gArea, 'sqft', gRates.p);
if(useGC1) sumGarage += pushRow(rows,'Garage — Coat 1', gArea, 'sqft', gRates.c1);
if(useGC2) sumGarage += pushRow(rows,'Garage — Coat 2', gArea, 'sqft', gRates.c2);
$('garageOut').innerText = `LF: ${gLF.toFixed(2)} | Area: ${gArea.toFixed(2)}`;
// Exterior (two levels optional)
const extA1 = n($('extPerim').value) * n($('extHeight').value);
const extA2 = n($('extPerim2').value) * n($('extHeight2').value);
const extArea = extA1 + extA2;
const extRates = { p:n($('rateExtP').value), c1:n($('rateExtC1').value), c2:n($('rateExtC2').value) };
const useEP = $('extUseP').value==='1', useEC1=$('extUseC1').value==='1', useEC2=$('extUseC2').value==='1';
let sumExt=0;
if(useEP) sumExt += pushRow(rows,'Exterior — Primer', extArea, 'sqft', extRates.p);
if(useEC1) sumExt += pushRow(rows,'Exterior — Coat 1', extArea, 'sqft', extRates.c1);
if(useEC2) sumExt += pushRow(rows,'Exterior — Coat 2', extArea, 'sqft', extRates.c2);
$('extOut').innerText = `Exterior Area: ${extArea.toFixed(2)} (P1: ${extA1.toFixed(0)} + P2: ${extA2.toFixed(0)})`;
// Totals
const subtotal = sumWalls + sumCeil + sumTrim + sumGarage + sumExt;
const markup = subtotal * markupPct;
const taxedBase = subtotal + markup;
const tax = taxedBase * taxPct;
const total = taxedBase + tax;
$('sumWalls').innerText = fmt(sumWalls);
$('sumCeil').innerText = fmt(sumCeil);
$('sumTrim').innerText = fmt(sumTrim);
$('sumGarage').innerText = fmt(sumGarage);
$('sumExt').innerText = fmt(sumExt);
$('sumSub').innerText = fmt(subtotal);
$('sumMarkup').innerText = fmt(markup);
$('sumTax').innerText = fmt(tax);
$('sumTotal').innerText = fmt(total);
// Detail table
const tbody = document.querySelector('#detailTable tbody');
tbody.innerHTML = '';
rows.forEach(r=>{
const tr=document.createElement('tr');
tr.innerHTML = `<td>${r.label}</td><td>${r.qty.toLocaleString(undefined,{maximumFractionDigits:2})}</td><td>${r.unit}</td><td style="white-space:nowrap">${fmt(r.rate)}</td><td style="white-space:nowrap">${fmt(r.cost)}</td>`;
tbody.appendChild(tr);
});
$('tblSub').innerText = fmt(subtotal);
// autosave
save();
return {subtotal, markup, tax, total, rows};
}
// wiring
$('calcBtn').addEventListener('click', calc);
$('resetBtn').addEventListener('click', ()=>{ localStorage.removeItem(storeKey); location.reload(); });
$('exportBtn').addEventListener('click', ()=>{
const out = calc();
const data = {
project: $('projectName').value,
currency: $('currency').value,
decimals: n($('decimals').value),
totals: { subtotal: out.subtotal, markup:n($('markupPct').value)/100, tax:n($('taxPct').value)/100, total: out.total },
rows: out.rows
};
const blob = new Blob([JSON.stringify(data,null,2)], {type:'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href=url; a.download='estimate.json'; a.click();
URL.revokeObjectURL(url);
});
// Show custom height box when needed
$('wallsHeight').addEventListener('change', (e)=>{
const v=e.target.value; $('wallsHeightCustom').style.display = (v==='custom')? 'block':'none';
});
// Auto-calc on input changes
ids.forEach(id=>{ const el=$(id); if(!el) return; el.addEventListener('input', ()=>{ if(id==='wallsHeight') return; calc(); }); el.addEventListener('change', calc); });
// init
load();
calc();
})();
</script>
</body>
</html>