/* ============================================================ ConcreteQuantity Calculator V5.3 - Working steps shown under every result - Input validation (blank / zero / negative / unreasonable) - Custom waste % beside 5 / 10 / 15 - Unit toggle converts entered values (incl. yd3 and price) - Metric mode: m³ inputs, 25 kg / 20 kg bags, 8 m³ mixers, litres - Currency toggle $ / £ - Mix dry-volume factor 1.54 (stated in UI) - Rebar assumptions stated (material estimate, laps not counted) ============================================================ */ (function(){ "use strict"; /* ---- calculation engine ---- */ var CC = (function(){ var BAG = { b40: 0.30, b60: 0.45, b80: 0.60 }; // ft3 per bag, QUIKRETE spec var BAGKG = { k25: 0.0117, k20: 0.0094 }; // m3 per bag, UK 25kg/20kg function num(v){ v = parseFloat(v); return (isFinite(v) && v > 0) ? v : 0; } function slabFt3(l,w,d){ l=num(l);w=num(w);d=num(d); return l*w*(d/12); } function rectFt3(l,w,h){ l=num(l);w=num(w);h=num(h); return l*w*h; } function wallFt3(l,h,t){ l=num(l);h=num(h);t=num(t); return l*h*(t/12); } function curbFt3(l,w,h){ l=num(l);w=num(w);h=num(h); return l*(w/12)*(h/12); } function cylFt3(d,h){ d=num(d);h=num(h); var r=(d/12)/2; return Math.PI*r*r*h; } function circleSlabFt3(d,t){ d=num(d);t=num(t); var r=d/2; return Math.PI*r*r*(t/12); } function stairsFt3(s,tr,ri,w){ s=num(s);tr=num(tr);ri=num(ri);w=num(w); return (tr/12)*(ri/12)*(s*(s+1)/2)*w; } /* V5.3: epsilon guards true-integer float artifacts (e.g. 22.000000000004 -> 22) */ function bagsNeeded(ft3){ ft3=num(ft3); return { b40: Math.ceil(ft3/BAG.b40 - 1e-9)||0, b60: Math.ceil(ft3/BAG.b60 - 1e-9)||0, b80: Math.ceil(ft3/BAG.b80 - 1e-9)||0 }; } function bagsNeededM3(m3){ m3=num(m3); return { k25: Math.ceil(m3/BAGKG.k25 - 1e-9)||0, k20: Math.ceil(m3/BAGKG.k20 - 1e-9)||0 }; } function mixForFt3(wet){ wet=num(wet); var dry=wet*1.54; return { dryFt3:dry, cementFt3:dry/7, sandFt3:dry*2/7, aggFt3:dry*4/7 }; } function rebarForSlab(l,w,sp){ l=num(l);w=num(w);sp=num(sp); if(!l||!w||!sp) return { totalLF:0, bars20:0, nW:0, nL:0 }; var aW=Math.ceil((w*12)/sp)+1, aL=Math.ceil((l*12)/sp)+1; var lf=aW*l+aL*w; return { totalLF:lf, bars20:Math.ceil(lf/20), nW:aW, nL:aL }; } return { num:num, slabFt3:slabFt3, rectFt3:rectFt3, wallFt3:wallFt3, curbFt3:curbFt3, cylFt3:cylFt3, circleSlabFt3:circleSlabFt3, stairsFt3:stairsFt3, bagsNeeded:bagsNeeded, bagsNeededM3:bagsNeededM3, mixForFt3:mixForFt3, rebarForSlab:rebarForSlab, yd3: function(f){return f/27;}, m3: function(f){return f*0.0283168;}, trucks: function(y){ y=num(y); return Math.ceil(y/10)||0; }, trucksM3: function(m){ m=num(m); return Math.ceil(m/8)||0; }, coverageFt2: function(y,t){ y=num(y);t=num(t); return t? (y*27)/(t/12):0; }, coverageM2: function(m,t){ m=num(m);t=num(t); return t? m/(t/100):0; } }; })(); /* ---- app state ---- */ var UNITS = 'imp'; // 'imp' | 'met' var CUR = '$'; // '$' | '£' var ULEN = { imp:'ft', met:'m' }, USM = { imp:'in', met:'cm' }; function toImpLen(v){ return UNITS==='met' ? v*3.28084 : v; } // m -> ft function toImpSm(v){ return UNITS==='met' ? v/2.54 : v; } // cm -> in /* ---- V5 supplemental styles (self-contained, no Customizer edit needed) ---- */ (function(){ if(document.getElementById('ccV5style')) return; var s=document.createElement('style'); s.id='ccV5style'; s.textContent='.cc-working{background:#f8fafc;border:1px dashed #94a3b8;border-radius:10px;' +'padding:10px 14px;margin:12px 0 4px;font-size:13.5px;line-height:1.65;color:#334155;overflow-wrap:anywhere}' +'.cc-working b{color:#1e40af}' +'.cc-error{background:#fef2f2;border:1px solid #fca5a5;color:#b91c1c;border-radius:10px;' +'padding:10px 14px;margin:12px 0 4px;font-size:14px;line-height:1.5}' +'.cc-wasterow{display:flex;gap:8px}.cc-wasterow .wasteSel{flex:1 1 auto;min-width:0}' +'.cc-wasterow .wasteCustom{flex:0 0 104px;width:104px}' +'#uCur{border:1px solid #e2e8f0;background:#ffffff;border-radius:10px;padding:9px 14px;' +'font-size:14px;font-weight:600;cursor:pointer;color:#0f172a;margin-left:6px;white-space:nowrap}' +'#uCur:hover{background:#f1f5f9}'; document.head.appendChild(s); })(); /* ---- labeled shape diagrams (like textbook figures) ---- */ var SERIF = 'font-style="italic" font-family="Georgia,serif" font-size="16" fill="#1e40af"'; /* ---- inline stroke icons (no external library) ---- */ var ICONS = { slab: '', footing: '', wall: '', column: '', posthole: '', circle: '', curb: '', stairs: '', bags: '', mix: '', rebar: '', cost: '', cube: '', copy: '', print: '', share: '', }; function boxSVG(a,b,c,ia,ib,ic){ function dim(t,id,x,y){ return ''+t+''; } return '' +'' +'' +'' +dim(a,ia,75,104)+dim(b,ib,142,54)+dim(c,ic,92,13)+''; } function tallBoxSVG(a,b,c,ia,ib,ic){ function dim(t,id,x,y){ return ''+t+''; } return '' +'' +'' +'' +dim(a,ia,81,104)+dim(b,ib,128,56)+dim(c,ic,90,8)+''; } function cylSVG(dLbl,hLbl,di,hi){ function dim(t,id,x,y){ return ''+t+''; } return '' +'' +'' +'' +'' +'' +'' +'' +dim(dLbl,di,68,18)+dim(hLbl,hi,122,60)+''; } function discSVG(dLbl,tLbl,di,ti){ function dim(t,id,x,y){ return ''+t+''; } return '' +'' +'' +'' +'' +'' +'' +dim(dLbl,di,68,30)+dim(tLbl,ti,126,58)+''; } function stairsSVG(trId,riId){ return '' +'tread' +'riser'; } function bagSVG(){ return '' +'' +'80 lb'; } function mixSVG(){ return '' +'' +'' +'' +'cement' +'sand' +'aggregate' +'1 : 2 : 4'; } function rebarSVG(sId){ return '' +'' +'' +'s'; } function costSVG(){ return '' +'$' +'' +'ft²'; } /* ---- mode definitions ---- kind: 'len' (ft/m), 'sm' (in/cm), 'num' (plain number), 'yd3' (yd3/m3), 'usd' (price, $/£) */ var MODES = [ { id:'slab', tab:'Slab', icon:'slab', title:'Concrete Slab Calculator', formula:'Volume = Length × Width × Thickness ÷ 27 (cubic yards)', diagram: boxSVG('l','h','w','l','d','w'), fields:[ {id:'l',label:'Length',kind:'len',ph:'10'}, {id:'w',label:'Width',kind:'len',ph:'10'}, {id:'d',label:'Thickness',kind:'sm',ph:'4'} ], vol: function(v){ return CC.slabFt3(toImpLen(v.l), toImpLen(v.w), toImpSm(v.d)); }, work: function(v){ var L=+v.l||0, W=+v.w||0, T=+v.d||0; if(UNITS==='met'){ var m3=L*W*(T/100); return fmt(L)+' × '+fmt(W)+' × '+fmt(T/100,3)+' m = '+fmt(m3,3)+' m³'; } var f=L*W*(T/12); return fmt(L)+' × '+fmt(W)+' × ('+fmt(T)+' ÷ 12) = '+fmt(f,1)+' ft³, then ÷ 27 = '+fmt(f/27)+' yd³'; } }, { id:'footing', tab:'Footing', icon:'footing', title:'Concrete Footing Calculator', formula:'Volume = Length × Width × Depth ÷ 27 (cubic yards)', diagram: boxSVG('l','d','w','l','d','w'), fields:[ {id:'l',label:'Length',kind:'len',ph:'20'}, {id:'w',label:'Width',kind:'len',ph:'2'}, {id:'d',label:'Depth',kind:'sm',ph:'12'} ], vol: function(v){ return CC.rectFt3(toImpLen(v.l), toImpLen(v.w), toImpSm(v.d)/12); }, work: function(v){ var L=+v.l||0, W=+v.w||0, D=+v.d||0; if(UNITS==='met'){ var m3=L*W*(D/100); return fmt(L)+' × '+fmt(W)+' × '+fmt(D/100,3)+' m = '+fmt(m3,3)+' m³'; } var f=L*W*(D/12); return fmt(L)+' × '+fmt(W)+' × ('+fmt(D)+' ÷ 12) = '+fmt(f,1)+' ft³, then ÷ 27 = '+fmt(f/27)+' yd³'; } }, { id:'wall', tab:'Wall', icon:'wall', title:'Concrete Wall Calculator', formula:'Volume = Length × Height × Thickness ÷ 27 (cubic yards)', diagram: tallBoxSVG('l','h','t','l','h','t'), fields:[ {id:'l',label:'Length',kind:'len',ph:'20'}, {id:'h',label:'Height',kind:'len',ph:'8'}, {id:'t',label:'Thickness',kind:'sm',ph:'8'} ], vol: function(v){ return CC.wallFt3(toImpLen(v.l), toImpLen(v.h), toImpSm(v.t)); }, work: function(v){ var L=+v.l||0, H=+v.h||0, T=+v.t||0; if(UNITS==='met'){ var m3=L*H*(T/100); return fmt(L)+' × '+fmt(H)+' × '+fmt(T/100,3)+' m = '+fmt(m3,3)+' m³'; } var f=L*H*(T/12); return fmt(L)+' × '+fmt(H)+' × ('+fmt(T)+' ÷ 12) = '+fmt(f,1)+' ft³, then ÷ 27 = '+fmt(f/27)+' yd³'; } }, { id:'column', tab:'Column', icon:'column', title:'Concrete Column / Pier Calculator', formula:'Volume = π × r² × Height (cylinder)', diagram: cylSVG('d','h','d','h'), fields:[ {id:'d',label:'Diameter',kind:'sm',ph:'12'}, {id:'h',label:'Height',kind:'len',ph:'4'}, {id:'n',label:'How many',kind:'num',ph:'1'} ], vol: function(v){ var n=CC.num(v.n)||1; return CC.cylFt3(toImpSm(v.d), toImpLen(v.h))*n; }, work: function(v){ var D=+v.d||0, H=+v.h||0, N=Math.round(+v.n)||1; if(UNITS==='met'){ var r=(D/100)/2, m3=Math.PI*r*r*H*N; return 'π × '+fmt(r,3)+'² × '+fmt(H)+' × '+N+' = '+fmt(m3,3)+' m³'; } var r2=(D/12)/2, f=Math.PI*r2*r2*H*N; return 'π × '+fmt(r2,3)+'² × '+fmt(H)+' × '+N+' = '+fmt(f,2)+' ft³, then ÷ 27 = '+fmt(f/27)+' yd³'; } }, { id:'posthole', tab:'Post Holes', icon:'posthole', title:'Post Hole Concrete Calculator', formula:'Each hole = π × r² × Depth. Great for fences & decks.', diagram: cylSVG('d','h','d','h'), fields:[ {id:'d',label:'Hole diameter',kind:'sm',ph:'10'}, {id:'h',label:'Hole depth',kind:'sm',ph:'36'}, {id:'n',label:'Number of holes',kind:'num',ph:'8'} ], vol: function(v){ var n=CC.num(v.n)||1; return CC.cylFt3(toImpSm(v.d), toImpSm(v.h)/12)*n; }, work: function(v){ var D=+v.d||0, H=+v.h||0, N=Math.round(+v.n)||1; if(UNITS==='met'){ var r=(D/100)/2, m3=Math.PI*r*r*(H/100)*N; return 'π × '+fmt(r,3)+'² × '+fmt(H/100,3)+' × '+N+' holes = '+fmt(m3,3)+' m³'; } var r2=(D/12)/2, f=Math.PI*r2*r2*(H/12)*N; return 'π × '+fmt(r2,3)+'² × ('+fmt(H)+' ÷ 12) × '+N+' holes = '+fmt(f,2)+' ft³, then ÷ 27 = '+fmt(f/27)+' yd³'; } }, { id:'circle', tab:'Circle Slab', icon:'circle', title:'Circular Slab Calculator', formula:'Volume = π × r² × Thickness ÷ 27 (cubic yards)', diagram: discSVG('d','t','d','t'), fields:[ {id:'d',label:'Diameter',kind:'len',ph:'10'}, {id:'t',label:'Thickness',kind:'sm',ph:'4'} ], vol: function(v){ return CC.circleSlabFt3(toImpLen(v.d), toImpSm(v.t)); }, work: function(v){ var D=+v.d||0, T=+v.t||0; if(UNITS==='met'){ var r=D/2, m3=Math.PI*r*r*(T/100); return 'π × '+fmt(r)+'² × '+fmt(T/100,3)+' = '+fmt(m3,3)+' m³'; } var r2=D/2, f=Math.PI*r2*r2*(T/12); return 'π × '+fmt(r2)+'² × ('+fmt(T)+' ÷ 12) = '+fmt(f,1)+' ft³, then ÷ 27 = '+fmt(f/27)+' yd³'; } }, { id:'curb', tab:'Curb', icon:'curb', title:'Concrete Curb Calculator', formula:'Volume = Length × Width × Height ÷ 27 (cubic yards)', diagram: boxSVG('l','h','w','l','h','w'), fields:[ {id:'l',label:'Length',kind:'len',ph:'100'}, {id:'w',label:'Width',kind:'sm',ph:'6'}, {id:'h',label:'Height',kind:'sm',ph:'6'} ], vol: function(v){ return CC.curbFt3(toImpLen(v.l), toImpSm(v.w), toImpSm(v.h)); }, work: function(v){ var L=+v.l||0, W=+v.w||0, H=+v.h||0; if(UNITS==='met'){ var m3=L*(W/100)*(H/100); return fmt(L)+' × '+fmt(W/100,3)+' × '+fmt(H/100,3)+' m = '+fmt(m3,3)+' m³'; } var f=L*(W/12)*(H/12); return fmt(L)+' × ('+fmt(W)+' ÷ 12) × ('+fmt(H)+' ÷ 12) = '+fmt(f,1)+' ft³, then ÷ 27 = '+fmt(f/27)+' yd³'; } }, { id:'stairs', tab:'Stairs', icon:'stairs', title:'Concrete Stairs Calculator (Solid Steps)', formula:'Solid steps = tread × riser × n(n+1)/2 × width', diagram: stairsSVG('tr','ri'), fields:[ {id:'s',label:'Steps',kind:'num',ph:'5'}, {id:'tr',label:'Tread depth',kind:'sm',ph:'11'}, {id:'ri',label:'Riser height',kind:'sm',ph:'7'}, {id:'w',label:'Width',kind:'len',ph:'4'} ], vol: function(v){ return CC.stairsFt3(CC.num(v.s), toImpSm(v.tr), toImpSm(v.ri), toImpLen(v.w)); }, work: function(v){ var S=+v.s||0, TR=+v.tr||0, RI=+v.ri||0, W=+v.w||0; if(UNITS==='met'){ var m3=(TR/100)*(RI/100)*(S*(S+1)/2)*W; return 'Tread × riser × '+S+'×'+(S+1)+'/2 × width = '+fmt(m3,3)+' m³ (solid steps)'; } var f=(TR/12)*(RI/12)*(S*(S+1)/2)*W; return 'Tread × riser × '+S+'×'+(S+1)+'/2 × width = '+fmt(f,1)+' ft³, then ÷ 27 = '+fmt(f/27)+' yd³ (solid steps)'; } }, { id:'bags', tab:'Bags', icon:'bags', title:'How Many Bags of Concrete?', formula:'Bags = volume ÷ bag yield (80 lb ≈ 0.60 ft³, 60 lb ≈ 0.45 ft³, 40 lb ≈ 0.30 ft³; metric: 25 kg ≈ 0.0117 m³, 20 kg ≈ 0.0094 m³)', diagram: bagSVG(), custom:'bags', empty:'Enter the volume above.', fields:[ {id:'y',label:'Concrete needed',kind:'yd3',ph:'1.25'} ] }, { id:'mix', tab:'Mix Ratio', icon:'mix', title:'On-Site Mix Calculator (1:2:4)', formula:'For site-mixed concrete: 1 part cement : 2 sand : 4 aggregate (by volume). Dry-volume factor 1.54.', diagram: mixSVG(), custom:'mix', empty:'Enter the volume above.', fields:[ {id:'y',label:'Concrete needed',kind:'yd3',ph:'1'} ] }, { id:'rebar', tab:'Rebar', icon:'rebar', title:'Rebar Calculator for Slabs', formula:'Bars each way = ⌈dimension ÷ spacing⌉ + 1 (rounded up for edge cover). Material estimate only, not structural design. Standard bar length 20 ft.', diagram: rebarSVG('s'), custom:'rebar', empty:'Enter slab size and spacing above.', fields:[ {id:'l',label:'Slab length',kind:'len',ph:'20'}, {id:'w',label:'Slab width',kind:'len',ph:'20'}, {id:'s',label:'Bar spacing',kind:'sm',ph:'18'} ] }, { id:'cost', tab:'Cost & Area', icon:'cost', title:'Cost Estimator & Coverage', formula:'Cost = volume × price. Coverage: area = volume ÷ thickness.', diagram: costSVG(), custom:'cost', empty:'Enter volume above (plus price or thickness).', optional:['p','t'], fields:[ {id:'y',label:'Concrete needed',kind:'yd3',ph:'5'}, {id:'p',label:'Price per yd³',kind:'usd',ph:'150'}, {id:'t',label:'Thickness (for coverage)',kind:'sm',ph:'4'} ] } ]; /* ---- rendering ---- */ function fmt(v, d){ if(!isFinite(v)) return '0'; var s = v.toFixed(d===undefined?2:d); if(s.indexOf('.')>=0) s = s.replace(/\.?0+$/,''); return s; } function unitFor(kind){ if(kind==='len') return ULEN[UNITS]; if(kind==='sm') return USM[UNITS]; if(kind==='yd3') return UNITS==='met' ? 'm³' : 'yd³'; return ''; } function fieldHTML(f){ var u = unitFor(f.kind); var suffix = ''; if(f.kind==='usd') suffix = ''+CUR+''; else if(u) suffix = ''+u+''; return '
'+ '
'+ suffix+'
'; } function volExtrasHTML(){ return '
'+ '
'+ '
'+ '
'+ '
'+ ''+CUR+'
'; } function bagsHTML(b){ if(UNITS==='met') return '
'+b.k25+'25 kg bags
'+ '
'+b.k20+'20 kg bags
'; return '
'+b.b80+'80 lb bags
'+ '
'+b.b60+'60 lb bags
'+ '
'+b.b40+'40 lb bags
'; } function volResultsHTML(ft3, waste, price, mode, panel, vals){ if(!(ft3>0)) return '
Enter your measurements above to see results instantly.
'; var met = UNITS==='met', w = 1+waste/100, f2 = ft3*w; var yd3 = f2/27, m3 = f2*0.0283168; var bags = met ? CC.bagsNeededM3(m3) : CC.bagsNeeded(ft3*w); var loads = met ? CC.trucksM3(m3) : CC.trucks(yd3); var bagN = met ? bags.k25 : bags.b80; var bagLbl = met ? '25 kg Bags' : '80 lb Bags'; var loadLbl = met ? '8 m³ mixer load' : 'truck load'; var cost = price>0 ? (met ? m3*price : yd3*price) : 0; var h = '

You need'+(waste>0?' (incl. '+waste+'% waste)':'')+'

'; h += '
' +'
'+ICONS.cube+''+fmt(yd3)+'Cubic Yards
' +'
'+ICONS.cube+''+fmt(m3)+'Cubic Metres
' +'
'+ICONS.bags+''+bagN+''+bagLbl+'
' +'
'; h += '
Working: '+mode.work(vals) +(waste>0 ? ' → +'+waste+'% waste allowance' : '')+'
'; h += '
'+fmt(f2,1)+' ft³'+ ''+loads+' '+loadLbl+(loads===1?'':'s')+''; if(cost>0) h += ''+CUR+fmt(cost)+' material cost'; h += '
'+bagsHTML(bags)+actionsHTML()+'
'; panel._summary = mode.title+': '+fmt(yd3)+' cubic yards ('+fmt(f2,1)+' cu ft, '+fmt(m3)+' cu m)' +(waste>0?' incl. '+waste+'% waste':'')+'. Bags: '+bagN+' x '+(met?'25 kg':'80 lb') +'. via concretequantity.com/concrete-calculator/'; return h; } function actionsHTML(){ return '
' +'' +'' +'
'; } function doAction(act, btn, panel){ var txt = panel._summary || 'Concrete Calculator: concretequantity.com/concrete-calculator/'; if(act==='print'){ window.print(); return; } if(act==='share' && navigator.share){ navigator.share({title:'Concrete Calculator', text:txt}).catch(function(){}); return; } var done = function(ok){ var sp = btn.querySelector('span'), old = sp.textContent; btn.classList.add('ok'); sp.textContent = ok ? 'Copied' : 'Copy failed'; setTimeout(function(){ btn.classList.remove('ok'); sp.textContent = old; }, 1600); }; if(navigator.clipboard && navigator.clipboard.writeText){ navigator.clipboard.writeText(txt).then(function(){ done(true); }, function(){ done(false); }); } else { var ta = document.createElement('textarea'); ta.value = txt; ta.style.position = 'fixed'; ta.style.opacity = '0'; document.body.appendChild(ta); ta.select(); try{ done(document.execCommand('copy')); }catch(e){ done(false); } document.body.removeChild(ta); } } function hlDim(panel, fid, on){ panel.querySelectorAll('.cc-diagram [data-dim]').forEach(function(g){ g.classList.toggle('hl', !!(on && g.getAttribute('data-dim')===fid)); }); } /* ---- validation ---- */ var VLIMIT = { len:5000, sm:500, num:100000, yd3:100000, usd:1000000 }; // sanity caps, imperial function validateFields(mode, vals){ var opt = mode.optional||[]; var empty = '
'+(mode.empty||'Enter your measurements above to see results instantly.')+'
'; for(var i=0;i=0; if(raw===''||raw==null){ if(!isOpt) return empty; continue; } var x=parseFloat(raw); if(!isFinite(x)) return '
Please enter a number for “'+f.label.replace(/<[^>]*>/g,'')+'”.
'; if(x<0) return '
“'+f.label.replace(/<[^>]*>/g,'')+'” cannot be negative.
'; if(x===0){ if(!isOpt) return '
“'+f.label.replace(/<[^>]*>/g,'')+'” must be greater than zero.
'; continue; } var imp=x; if(f.kind==='len'&&UNITS==='met') imp=x*3.28084; else if(f.kind==='sm'&&UNITS==='met') imp=x/2.54; else if(f.kind==='yd3'&&UNITS==='met') imp=x*1.30795; if(imp>(VLIMIT[f.kind]||100000)) return '
“'+f.label.replace(/<[^>]*>/g,'')+'” looks unusually large. Please check the value.
'; } return null; } /* ---- per-panel calculation ---- */ function calcPanel(mode, panel){ var vals = {}; panel.querySelectorAll('input[data-fid]').forEach(function(i){ vals[i.getAttribute('data-fid')] = i.value.trim(); }); var box = panel.querySelector('.cc-out'); var verr = validateFields(mode, vals); if(verr){ box.innerHTML = verr; panel._summary=''; return; } var met = UNITS==='met', h = ''; if(mode.custom==='bags'){ var yb=CC.num(vals.y), m3b = met? yb : yb*0.764555, ft3b = met? yb*35.3147 : yb*27; var bb = met? CC.bagsNeededM3(m3b) : CC.bagsNeeded(ft3b); var bw = met ? fmt(yb)+' m³ ÷ 0.0117 m³ = '+bb.k25+' × 25 kg bags (÷ 0.0094 = '+bb.k20+' × 20 kg)' : fmt(yb)+' yd³ × 27 = '+fmt(ft3b,1)+' ft³ ÷ 0.60 ft³ = '+bb.b80+' × 80 lb bags'; h = '

Bags needed

'+bagsHTML(bb)+ '
Working: '+bw+'
'+ '

Based on '+fmt(ft3b,1)+' ft³ ('+fmt(m3b,3)+' m³). Bag yields: '+ (met?'25 kg ≈ 0.0117 m³, 20 kg ≈ 0.0094 m³.':'80 lb ≈ 0.60 ft³, 60 lb ≈ 0.45 ft³, 40 lb ≈ 0.30 ft³.')+'

'+ actionsHTML()+'
'; panel._summary = 'Bags for '+fmt(yb)+(met?' cubic metres':' cubic yards')+': ' +(met? bb.k25+' x 25 kg, '+bb.k20+' x 20 kg' : bb.b80+' x 80 lb, '+bb.b60+' x 60 lb, '+bb.b40+' x 40 lb') +'. via concretequantity.com/concrete-calculator/'; } else if(mode.custom==='mix'){ var ym=CC.num(vals.y), wetFt3 = met? ym*35.3147 : ym*27; var mx = CC.mixForFt3(wetFt3), body='', mw=''; if(met){ var cemKg = mx.cementFt3*0.0283168*1440, b25 = Math.ceil(cemKg/25)||0; var sM3 = mx.sandFt3*0.0283168, aM3 = mx.aggFt3*0.0283168, wL = Math.round(cemKg*0.5); body = ''+b25+' cement bags (25 kg)'+ ''+fmt(sM3)+' m³ sand'+ ''+fmt(aM3)+' m³ aggregate'+ ''+wL+' litres water (approx)'; mw = 'Dry volume = '+fmt(ym)+' × 1.54 = '+fmt(mx.dryFt3*0.0283168,2)+' m³. '+ 'Cement = ÷ 7 = '+fmt(mx.cementFt3*0.0283168,3)+' m³ ≈ '+fmt(cemKg,0)+' kg ≈ '+b25+' × 25 kg bags.'; panel._summary = 'Site mix (1:2:4) for '+fmt(ym)+' cubic metres: '+b25+' cement bags (25 kg), ' +fmt(sM3)+' m3 sand, '+fmt(aM3)+' m3 aggregate. via concretequantity.com/concrete-calculator/'; } else { var cb = Math.ceil(mx.cementFt3)||0, sY = mx.sandFt3/27, aY = mx.aggFt3/27, wg = cb*6; body = ''+cb+' cement bags (94 lb)'+ ''+fmt(sY)+' yd³ sand'+ ''+fmt(aY)+' yd³ aggregate'+ ''+wg+' gal water (approx)'; mw = 'Dry volume = '+fmt(ym)+' yd³ × 27 × 1.54 = '+fmt(mx.dryFt3,1)+' ft³. '+ 'Cement = ÷ 7 = '+fmt(mx.cementFt3,1)+' ft³ ≈ '+cb+' bags (94 lb) (1 ft³ ≈ 1 bag).'; panel._summary = 'Site mix (1:2:4) for '+fmt(ym)+' cubic yards: '+cb +' cement bags, '+fmt(sY)+' yd3 sand, '+fmt(aY)+' yd3 aggregate. via concretequantity.com/concrete-calculator/'; } h = '

Site-mix materials (1:2:4)

'+body+'
'+ '
Working: '+mw+'
'+actionsHTML()+'
'; } else if(mode.custom==='rebar'){ var rb = CC.rebarForSlab(toImpLen(vals.l), toImpLen(vals.w), toImpSm(vals.s)); if(rb.totalLF>0){ var big, sub, rw; if(met){ var Lm=+vals.l||0, Wm=+vals.w||0, Scm=+vals.s||0; big = fmt(rb.totalLF*0.3048,0)+' metres'; sub = ''+rb.bars20+' × 20-ft bars'; rw = 'Across '+fmt(Wm)+' m @ '+fmt(Scm)+' cm: ⌊'+fmt(Wm*100,0)+' ÷ '+fmt(Scm)+'⌋ + 1 = '+rb.nW+ ' bars × '+fmt(Lm)+' m. Across '+fmt(Lm)+' m: '+rb.nL+' bars × '+fmt(Wm)+ ' m = '+fmt(rb.totalLF*0.3048,0)+' m ('+fmt(rb.totalLF,0)+' ft).'; } else { var Li=+vals.l||0, Wi=+vals.w||0, Si=+vals.s||0; big = fmt(rb.totalLF,0)+' linear feet'; sub = ''+rb.bars20+' × 20-ft bars'; rw = 'Across '+fmt(Wi)+' ft @ '+fmt(Si)+' in: ⌊'+fmt(Wi*12,0)+' ÷ '+fmt(Si)+'⌋ + 1 = '+rb.nW+ ' bars × '+fmt(Li)+' ft. Across '+fmt(Li)+' ft: '+rb.nL+' bars × '+fmt(Wi)+ ' ft. Total = '+fmt(rb.totalLF,0)+' ft.'; } h = '

Rebar estimate

'+ '
'+big+'
'+ '
'+sub+'
'+ '
Working: '+rw+'
'+ '

Material estimate only, not structural design. Bars run the full slab length both ways; laps are not counted, so add about 10% extra for laps and waste.

'+ actionsHTML()+'
'; panel._summary = 'Rebar estimate: '+fmt(rb.totalLF,0)+' linear feet ('+rb.bars20 +' x 20-ft bars). via concretequantity.com/concrete-calculator/'; } else h = '
'+mode.empty+'
'; } else if(mode.custom==='cost'){ var yc=CC.num(vals.y), pc=CC.num(vals.p), tc=vals.t, tImp=toImpSm(tc); h = '

Estimate

'; var parts=[], okc=false, sumc=''; if(yc>0 && pc>0){ h += 'Material cost: '+CUR+fmt(yc*pc)+''; parts.push(fmt(yc)+(met?' m³':' yd³')+' × '+CUR+fmt(pc)+' = '+CUR+fmt(yc*pc)+''); sumc = 'cost '+CUR+fmt(yc*pc); okc=true; } if(yc>0 && tImp>0){ if(met){ var m2=CC.coverageM2(yc, +tc); h += 'Covers: '+fmt(m2,0)+' m² at '+fmt(+tc)+' cm thick'; parts.push('Coverage = '+fmt(yc)+' ÷ ('+fmt(+tc)+' ÷ 100) = '+fmt(m2,0)+' m²'); sumc += (sumc?', ':'')+'covers '+fmt(m2,0)+' sq m'; } else { var f2c=CC.coverageFt2(yc,tImp); h += 'Covers: '+fmt(f2c,0)+' ft² at '+fmt(+tc)+' '+USM[UNITS]+' thick'; parts.push('Coverage = '+fmt(yc)+' × 27 ÷ ('+fmt(+tc)+' ÷ 12) = '+fmt(f2c,0)+' ft²'); sumc += (sumc?', ':'')+'covers '+fmt(f2c,0)+' sq ft'; } okc=true; } h += '
'; if(okc){ if(parts.length) h += '
Working: '+parts.join('
')+'
'; h += actionsHTML(); panel._summary = 'Concrete estimate: '+fmt(yc)+(met?' cubic metres':' cubic yards') +(sumc? ', '+sumc : '')+'. via concretequantity.com/concrete-calculator/'; } else h += '
'+mode.empty+'
'; h += '
'; } else { var vft3 = mode.vol(vals); var wsel = panel.querySelector('.wasteSel'), wcus = panel.querySelector('.wasteCustom'); var wv = wcus ? parseFloat(wcus.value) : NaN; var waste = (isFinite(wv)&&wv>0) ? Math.min(wv,100) : (wsel ? (parseFloat(wsel.value)||0) : 0); var praw = panel.querySelector('.priceInp').value.trim(), price=0; if(praw!==''){ var px=parseFloat(praw); if(!isFinite(px)||px<0){ box.innerHTML='
Price must be zero or greater.
'; panel._summary=''; return; } if(px>1000000){ box.innerHTML='
Price looks unusually large. Please check it.
'; panel._summary=''; return; } price=px; } h = volResultsHTML(vft3, waste, price, mode, panel, vals); } box.innerHTML = h; } /* ---- panel + tabs ---- */ function buildPanel(m, first){ var p = document.createElement('div'); p.className = 'cc-panel'+(first?' on':''); p.id = 'ccp-'+m.id; var inner = '

'+m.title+'

'+m.formula+'

' +(m.diagram?'
'+m.diagram+'
':'')+'
'; m.fields.forEach(function(f){ inner += fieldHTML(f); }); inner += '
'; if(!m.custom) inner += volExtrasHTML(); inner += '
'; p.innerHTML = inner; p.querySelectorAll('input,select').forEach(function(el){ el.addEventListener('input', function(){ calcPanel(m, p); }); }); p.querySelectorAll('input[data-fid]').forEach(function(inp){ inp.addEventListener('focus', function(){ hlDim(p, inp.getAttribute('data-fid'), true); }); inp.addEventListener('blur', function(){ hlDim(p, inp.getAttribute('data-fid'), false); }); }); p.addEventListener('click', function(e){ var b = e.target && e.target.closest ? e.target.closest('.cc-act') : null; if(b) doAction(b.getAttribute('data-act'), b, p); }); calcPanel(m, p); return p; } function activateTab(id){ var tabs = document.getElementById('ccTabs'), panels = document.getElementById('ccPanels'); if(!tabs || !panels) return false; var ok = false; MODES.forEach(function(m){ if(m.id===id) ok = true; }); if(!ok) return false; tabs.querySelectorAll('button').forEach(function(b){ b.classList.toggle('on', b.getAttribute('data-mid')===id); }); panels.querySelectorAll('.cc-panel').forEach(function(p){ p.classList.toggle('on', p.id==='ccp-'+id); }); return true; } function buildTabs(){ var tabs = document.getElementById('ccTabs'), panels = document.getElementById('ccPanels'); if(!tabs || !panels) return; MODES.forEach(function(m, i){ var tb = document.createElement('button'); tb.type='button'; tb.innerHTML = (ICONS[m.icon]||'')+''+m.tab+''; tb.setAttribute('role','tab'); tb.setAttribute('data-mid', m.id); if(i===0) tb.classList.add('on'); tb.onclick = function(){ activateTab(m.id); try{ history.replaceState(null,'','#'+m.id); }catch(e){} }; tabs.appendChild(tb); panels.appendChild(buildPanel(m, i===0)); }); var h = (location.hash||'').replace('#',''); if(h) activateTab(h); window.addEventListener('hashchange', function(){ activateTab((location.hash||'').replace('#','')); }); } function buildSingle(){ var id = window.CC_SINGLE, m = null; MODES.forEach(function(x){ if(x.id===id) m = x; }); if(!m) return; var c = document.getElementById('ccSingle'); if(!c) return; c.appendChild(buildPanel(m, true)); } /* ---- units + currency ---- */ function recalcAll(){ MODES.forEach(function(m){ var p = document.getElementById('ccp-'+m.id); if(p) calcPanel(m, p); }); } function setUnits(u){ if(u===UNITS) return; document.querySelectorAll('#ccPanels input[data-kind], #ccSingle input[data-kind]').forEach(function(inp){ var v = parseFloat(inp.value); if(!isFinite(v)) return; var k = inp.getAttribute('data-kind'); if(k==='len') inp.value = fmt(u==='met' ? v*0.3048 : v*3.28084); else if(k==='sm') inp.value = fmt(u==='met' ? v*2.54 : v/2.54); else if(k==='yd3') inp.value = fmt(u==='met' ? v*0.764555 : v*1.30795); else if(k==='usd') inp.value = fmt(u==='met' ? v*1.30795 : v/1.30795); }); UNITS = u; var ui=document.getElementById('uImp'), um=document.getElementById('uMet'); if(ui) ui.classList.toggle('on', u==='imp'); if(um) um.classList.toggle('on', u==='met'); document.querySelectorAll('#ccPanels span[data-u], #ccSingle span[data-u]').forEach(function(sp){ sp.textContent = unitFor(sp.getAttribute('data-u')); }); document.querySelectorAll('#ccPanels .upl, #ccSingle .upl').forEach(function(sp){ sp.textContent = u==='met' ? 'm³' : 'yd³'; }); recalcAll(); } function setCur(c){ CUR = c; var b=document.getElementById('uCur'); if(b) b.textContent = (c==='£' ? '£ GBP' : '$ USD'); document.querySelectorAll('.curSym').forEach(function(s){ s.textContent = c; }); recalcAll(); } function buildCurToggle(){ var u=document.querySelector('.cc-units'); if(!u || document.getElementById('uCur')) return; var b=document.createElement('button'); b.type='button'; b.id='uCur'; b.textContent='$ USD'; b.setAttribute('aria-label','Toggle currency between US dollars and British pounds'); b.onclick=function(){ setCur(CUR==='$' ? '£' : '$'); }; u.appendChild(b); } /* ---- init ---- */ var _uImp=document.getElementById('uImp'), _uMet=document.getElementById('uMet'); if(_uImp) _uImp.onclick = function(){ setUnits('imp'); }; if(_uMet) _uMet.onclick = function(){ setUnits('met'); }; buildCurToggle(); if(window.CC_SINGLE){ buildSingle(); } else { buildTabs(); } })();