if(n!==0&&Math.abs(n)<0.000001){return n.toExponential(3);}
return FMT?FMT.format(n):String(n);
}
function parseNum(raw){
var s=String(raw).replace(/[\s\u00a0]/g,'');
if(s.charAt(s.length-1)==='%'){s=s.slice(0,-1);}
if(s===''){return {empty:true};}
var hasComma=s.indexOf(',')!==-1,hasDot=s.indexOf('.')!==-1;
if(hasComma&&hasDot){
if(s.lastIndexOf(',')>s.lastIndexOf('.')){s=s.replace(/\./g,'').replace(',','.');}
else{s=s.replace(/,/g,'');}
}else if(hasComma){
if(/^[+-]?\d{1,3}(,\d{3})+$/.test(s)){s=s.replace(/,/g,'');}
else{s=s.replace(',','.');}
}
if(!/^[+-]?(\d+\.?\d*|\.\d+)$/.test(s)){return {invalid:true};}
var n=Number(s);
if(!isFinite(n)){return {invalid:true};}
return {value:n};
}
var MINUS=' \u2212 ',TIMES=' \u00d7 ',DIV=' \u00f7 ';
var CALCS={
percentOf:function(x,y){
var r=x/100*y;
return {num:r,main:fmt(r),detail:fmt(x)+'% of '+fmt(y)+' = '+fmt(x)+DIV+'100'+TIMES+fmt(y)+' = '+fmt(r)};
},
whatPercent:function(x,y){
if(y===0){return {error:'Y cannot be zero, because dividing by zero is not possible.'};}
var r=x/y*100;
return {num:r,main:fmt(r)+'%',detail:fmt(x)+' is '+fmt(r)+'% of '+fmt(y)+'. Formula: '+fmt(x)+DIV+fmt(y)+TIMES+'100 = '+fmt(r)+'%'};
},
change:function(x,y){
if(x===0){return {error:'X (the starting value) cannot be zero, because a percentage change from zero is undefined.'};}
var r=(y-x)/Math.abs(x)*100;
var rc=clean(r);
var word=rc>0?'an increase of ':'a decrease of ';
var formula='Formula: ('+fmt(y)+MINUS+fmt(x)+')'+DIV+fmt(Math.abs(x))+TIMES+'100 = '+fmt(r)+'%';
if(rc===0){return {num:r,main:'0%',detail:'No change from '+fmt(x)+' to '+fmt(y)+'. '+formula};}
return {num:r,main:(rc>0?'+':'')+fmt(r)+'%',detail:'From '+fmt(x)+' to '+fmt(y)+' is '+word+fmt(Math.abs(r))+'%. '+formula};
},
addSub:function(x,y,mode){
var amount=x*y/100;
var sub=(mode==='subtract');
var r=sub?x-amount:x+amount;
return {num:r,main:fmt(r),detail:fmt(x)+(sub?MINUS:' + ')+fmt(y)+'% = '+fmt(x)+(sub?MINUS:' + ')+fmt(amount)+' = '+fmt(r)};
},
ofWhat:function(x,y){
if(y===0){return {error:'Y (the percentage) cannot be zero, because dividing by zero is not possible.'};}
var r=x/(y/100);
return {num:r,main:fmt(r),detail:fmt(x)+' is '+fmt(y)+'% of '+fmt(r)+'. Formula: '+fmt(x)+DIV+'('+fmt(y)+DIV+'100) = '+fmt(r)}
}
};
function fallbackCopy(str,done){
var ta=document.createElement('textarea');
ta.value=str;ta.setAttribute('readonly','');
ta.style.position='fixed';ta.style.top='-1000px';ta.style.opacity='0';
root.appendChild(ta);ta.select();
try{ta.setSelectionRange(0,str.length);}catch(e){}
var ok=false;
try{ok=document.execCommand('copy');}catch(e){ok=false;}
root.removeChild(ta);
done(ok);
}
function copyText(str,done){
if(navigator.clipboard){
if(window.isSecureContext){
navigator.clipboard.writeText(str).then(function(){done(true);},function(){fallbackCopy(str,done);});
return;
}
}
fallbackCopy(str,done);
}
function setupCard(card){
var name=card.getAttribute('data-calc');
var inputs=Array.prototype.slice.call(card.querySelectorAll('input[data-field]'));
var sel=card.querySelector('select[data-field]');
var out=card.querySelector('.fl-pc__out');
var mainEl=card.querySelector('[data-main]');
var detailEl=card.querySelector('[data-detail]');
var btnCopy=card.querySelector('[data-act="copy"]');
var btnClear=card.querySelector('[data-act="clear"]');
var msgEl=card.querySelector('[data-msg]');
if(!CALCS[name]||!out||!mainEl||!detailEl||!btnCopy||!btnClear||inputs.length<2){throw new Error('a calculator card is incomplete');}
var copyValue='',timer,msgTimer;
function show(state,main,detail,copy){
out.className='fl-pc__out'+(state?' is-'+state:'');
mainEl.textContent=main;
detailEl.textContent=detail;
copyValue=copy;
btnCopy.disabled=!copy;
}
function update(){
var vals={},anyEmpty=false,bad=null;
inputs.forEach(function(inp){
inp.removeAttribute('aria-invalid');
var p=parseNum(inp.value);
if(p.empty){anyEmpty=true;return;}
if(p.invalid){
inp.setAttribute('aria-invalid','true');
if(!bad){bad=inp;}
return;
}
vals[inp.getAttribute('data-field')]=p.value;
});
if(bad){
show('error','','Please enter a valid number in the "'+bad.getAttribute('data-name')+'" box. Use digits with an optional minus sign and decimal point.','');
return;
}
if(anyEmpty){
show('','','Enter both numbers to see the result.','');
return;
}
var r;
try{r=CALCS[name](vals.x,vals.y,sel?sel.value:'');}catch(e){r={error:'Sorry, this could not be calculated.'};}
if(r.error){show('error','',r.error,'');return;}
if(!isFinite(r.num)){show('error','','The result is too large to display.','');return;}
show('ok',r.main,r.detail,r.main);
}
inputs.forEach(function(inp){
inp.addEventListener('input',function(){clearTimeout(timer);timer=setTimeout(update,250);});
});
if(sel){sel.addEventListener('change',update);}
btnCopy.addEventListener('click',function(){
if(!copyValue){return;}
copyText(copyValue,function(ok){
msgEl.textContent=ok?'Copied.':'Copy failed. Please copy it manually.';
msgEl.style.color=ok?'':'#b91c1c';
clearTimeout(msgTimer);
msgTimer=setTimeout(function(){msgEl.textContent='';},2500);
});
});
btnClear.addEventListener('click',function(){
clearTimeout(timer);
inputs.forEach(function(inp){inp.value='';});
update();
inputs[0].focus();
});
update();
}
cardEls.forEach(setupCard);
if(loadingEl){loadingEl.hidden=true;}
}
function init(root){
if(root.getAttribute('data-init')==='1'){return;}
root.setAttribute('data-init','1');
var loadingEl=root.querySelector('#fl-pc-loading');
try{
build(root,loadingEl);
}catch(err){
if(loadingEl){
loadingEl.hidden=false;
loadingEl.textContent='Sorry, this calculator could not start ('+(err&&err.message?err.message:'unknown error')+'). Please reload the page.';
}
}
}
function start(){
var root=document.getElementById('fl-pc');
if(!root){return false;}
init(root);
return true;
}
if(!start()){
var tries=0;
var tmr=setInterval(function(){
tries=tries+1;
if(start()||tries>=50){clearInterval(tmr);}
},200);
document.addEventListener('DOMContentLoaded',start);
}
})();