// ==UserScript== // @name Balansenergie All-in v4.6.5 // @namespace paq.balansenergie // @version 4.6.5 // @description All-in Resultaten-dashboard met voorlopige dagen, schakelbare all-in kwartierprijzen op Actueel en Absurd Units in het Balans-resultaat bij All-in AAN. // @homepageURL https://github.com/paqpaqpaq/BEdashboard // @supportURL https://github.com/paqpaqpaq/BEdashboard/issues // @updateURL https://raw.githubusercontent.com/paqpaqpaq/BEdashboard/main/Balansenergie-All-in.meta.js // @downloadURL https://raw.githubusercontent.com/paqpaqpaq/BEdashboard/main/Balansenergie-All-in.user.js // @match *://dashboard.balansenergie.nl/* // @run-at document-start // @grant GM.xmlHttpRequest // @grant GM_xmlhttpRequest // @connect api.energy-charts.info // ==/UserScript== (function(){ const launch=()=>{const n=document.createElement('script');n.textContent="(function () {\n 'use strict';\n\n /*\n * Safari Userscripts kan dezelfde userscriptbron zowel in de paginawereld\n * als via zijn content-fallback starten. Beide werelden delen de DOM.\n * Laat daarom precies één instantie de Resultaten-UI en zijn periodecache\n * beheren; anders kan een tweede instantie de gekozen jaarstand weer met\n * zijn eigen beginstand overschrijven.\n */\n var BE_RUNTIME_GUARD =\n 'data-be-allin-runtime';\n\n if (\n document.documentElement.hasAttribute(\n BE_RUNTIME_GUARD\n )\n ) {\n return;\n }\n\n document.documentElement.setAttribute(\n BE_RUNTIME_GUARD,\n '4.6.5'\n );\n\n // Colour-only theme: never alter dimensions, typography, positioning or SVG paths.\n var BE_DARK_KEY = 'be_cfg_darkmode';\n var beDarkOn = localStorage.getItem(BE_DARK_KEY) === 'aan';\n var beDarkSheet, beDarkObserver, beDarkTimer, beDarkPending = new Set();\n var beDarkRules = new Map();\n function beDarkColor(value, kind) {\n if (typeof value !== 'string') return value;\n value=value.trim();\n if(value==='white')value='rgb(255,255,255)';\n if(value==='black')value='rgb(0,0,0)';\n if(/^#[0-9a-f]{3,8}$/i.test(value)){\n var hex=value.slice(1);if(hex.length===3||hex.length===4)hex=hex.split('').map(function(c){return c+c;}).join('');\n value='rgba('+[0,2,4].map(function(i){return parseInt(hex.slice(i,i+2),16);}).join(',')+','+(hex.length===8?parseInt(hex.slice(6,8),16)/255:1)+')';\n }\n var srgb=value.match(/^color\\(srgb\\s+([\\d.]+)\\s+([\\d.]+)\\s+([\\d.]+)(?:\\s*\\/\\s*([\\d.]+))?\\)$/);\n if(srgb)value='rgba('+[1,2,3].map(function(i){return Math.round(+srgb[i]*255);}).join(',')+','+(srgb[4]||1)+')';\n var m=value.match(/^rgba?\\(\\s*([\\d.]+)[, ]+([\\d.]+)[, ]+([\\d.]+)(?:\\s*[,/]\\s*([\\d.]+))?\\s*\\)$/);\n if(!m)return value;\n var r=+m[1],g=+m[2],b=+m[3],a=m[4]==null?1:+m[4];\n if(!a)return value;\n var hi=Math.max(r,g,b),lo=Math.min(r,g,b),light=(r+g+b)/3;\n var neutral=hi-lo<48;\n var out;\n if(kind==='background') {\n if(r>b*.85&&b>r*.75&&b>g*1.12&&hi-lo>48)out=[139,115,78];\n else if(light>180)out=light>245?[55,50,43]:[69,64,54];\n else if(neutral&&light>95)out=[78,71,60];\n } else if(kind==='border') {\n if(light>150)out=[103,94,79];\n } else {\n if(neutral)out=light<90?[232,225,213]:light<205?[191,182,165]:null;\n else if(r>b*.85&&b>r*.75&&b>g*1.12){out=[203,181,143];}\n else if(g>r*1.15&&g>b*1.05){out=[51,192,113];}\n else if(r>g*1.4&&r>b*1.2){out=[242,79,91];}\n else if(light<190) {\n // Lift existing hue rather than replacing status/series colours.\n var t=light<80?.55:.40;\n out=[r,g,b].map(function(v){return Math.round(v+(255-v)*t);});\n }\n }\n return out?'rgba('+out.join(',')+','+a+')':value;\n }\n function beDarkRefresh() {\n if(!beDarkOn||!beDarkSheet)return;\n var roots=Array.from(beDarkPending);beDarkPending.clear();\n // Read the site's original colours synchronously; no unthemed frame is painted.\n beDarkSheet.disabled=true;\n var updates=[];\n try {\n var seen=new Set();\n roots.forEach(function(root){\n if(!root.isConnected)return;\n [root].concat(Array.from(root.querySelectorAll('*'))).forEach(function(e){\n if(seen.has(e)||/^(SCRIPT|STYLE|LINK|META|NOSCRIPT|CANVAS)$/.test(e.tagName))return;\n seen.add(e);\n var c=getComputedStyle(e),rules=[];\n function add(prop,val,kind){var next=beDarkColor(val,kind);if(next!==val)rules.push(prop+':'+next+'!important');}\n add('color',c.color,'text');add('background-color',c.backgroundColor,'background');\n ['Top','Right','Bottom','Left'].forEach(function(side){add('border-'+side.toLowerCase()+'-color',c['border'+side+'Color'],'border');});\n if(e.namespaceURI==='http://www.w3.org/2000/svg') {add('fill',c.fill,'text');add('stroke',c.stroke,'text');}\n // Preserve gradients and dotted patterns; change only their colour stops.\n if(c.backgroundImage&&c.backgroundImage!=='none'){\n var bg=c.backgroundImage.replace(/(?:rgba?|color)\\([^)]*\\)|#[0-9a-f]{3,8}\\b|\\bwhite\\b/gi,function(v){return beDarkColor(v,'background');});\n if(bg!==c.backgroundImage)rules.push('background-image:'+bg+'!important');\n }\n if(c.boxShadow&&c.boxShadow!=='none')rules.push('box-shadow:'+c.boxShadow.replace(/rgba?\\([^)]*\\)/g,'rgba(0,0,0,.18)')+'!important');\n // Keep toggle thumbs white; track hue and position communicate state.\n var width=parseFloat(c.width),height=parseFloat(c.height),radius=parseFloat(c.borderRadius);\n if(c.position==='absolute'&&width<=24&&height<=24&&radius>=6&&e.parentElement){\n var pc=getComputedStyle(e.parentElement);\n if(pc.position==='relative'&&parseFloat(pc.width)>=28&&parseFloat(pc.width)<=60&&parseFloat(pc.height)<=32)\n rules.push('background-color:#eee7da!important','box-shadow:0 1px 3px #0008!important');\n }\n if(c.position==='relative'&&width>=28&&width<=60&&height>=16&&height<=32&&radius>=8&&e.firstElementChild){\n var tc=getComputedStyle(e.firstElementChild);\n if(tc.position==='absolute'){\n var child=e.firstElementChild, explicit=e.getAttribute('aria-checked');\n var targetTransform=child.style.transform, targetLeft=child.style.left;\n var shift=targetTransform.match(/translateX\\(\\s*([-\\d.]+)/);\n var on=explicit!==null?explicit==='true':shift?+shift[1]>5:targetLeft?parseFloat(targetLeft)>5:parseFloat(tc.left)>5||(/matrix\\(/.test(tc.transform)&&parseFloat(tc.transform.split(',')[4])>5);\n rules.push('background-color:'+(on?'#9d8764':'#59554e')+'!important','border-color:#b3a58d!important');\n }\n }\n if(parseFloat(c.borderRadius)>=6&&parseFloat(c.borderTopWidth)>0&&width>160&&height>45)\n rules.push('background-color:#454036!important','background-image:none!important');\n updates.push([e,rules.join(';'),'']);\n ['::before','::after'].forEach(function(pseudo){\n var pc=getComputedStyle(e,pseudo);if(pc.content==='none'||pc.content==='normal')return;\n var pr=[],pb=beDarkColor(pc.backgroundColor,'background');\n if(pb!==pc.backgroundColor)pr.push('background-color:'+pb+'!important');\n var pi=pc.backgroundImage.replace(/(?:rgba?|color)\\([^)]*\\)|#[0-9a-f]{3,8}\\b|\\bwhite\\b/gi,function(v){return beDarkColor(v,'background');});\n if(pi!==pc.backgroundImage)pr.push('background-image:'+pi+'!important');\n updates.push([e,pr.join(';'),pseudo]);\n });\n });\n });\n } finally {beDarkSheet.disabled=false;}\n updates.forEach(function(pair){\n var e=pair[0],rule=pair[1],pseudo=pair[2]||'',attr='data-be-dark-colors'+pseudo.replace('::','-');if(!rule){e.removeAttribute(attr);return;}\n var id=beDarkRules.get(pseudo+rule);\n if(!id){id='c'+beDarkRules.size;beDarkRules.set(pseudo+rule,id);beDarkSheet.sheet.insertRule('html[data-be-dark=\"aan\"] ['+attr+'=\"'+id+'\"]'+pseudo+'{'+rule+'}',beDarkSheet.sheet.cssRules.length);}\n if(e.getAttribute(attr)!==id)e.setAttribute(attr,id);\n });\n }\n function beDarkQueue(root){\n if(!beDarkOn||!root||root.nodeType!==1||/^(SCRIPT|STYLE)$/.test(root.tagName))return;\n beDarkPending.add(root);\n if(!beDarkTimer){beDarkTimer=true;queueMicrotask(function(){beDarkTimer=null;beDarkRefresh();});}\n }\n function beDarkApply(on){\n beDarkOn=!!on;localStorage.setItem(BE_DARK_KEY,on?'aan':'uit');\n document.documentElement.setAttribute('data-be-dark',on?'aan':'uit');\n if(on&&document.body){beDarkPending.add(document.body);beDarkRefresh();}\n document.dispatchEvent(new Event('be-dark-change'));\n }\n function beDarkInit(){\n beDarkSheet=document.createElement('style');beDarkSheet.id='be-dark-style';\n beDarkSheet.textContent='html[data-be-dark=\"aan\"]{color-scheme:dark;background:#2c2924!important}html[data-be-dark=\"aan\"] body{background-color:#2c2924!important;color:#dddfe3!important}html[data-be-dark=\"aan\"] .summary-compact-metric{background:#454036!important;background-image:none!important;border-color:#675e4f!important}html[data-be-dark=\"aan\"] .summary-compact-label{color:#bfb6a5!important}html[data-be-dark=\"aan\"] body .summary-compact-metric.summary-compact-metric.summary-compact-metric.summary-compact-metric.summary-compact-metric.summary-compact-metric.summary-compact-metric.summary-compact-metric{background:#454036!important;background-image:none!important;border-color:#675e4f!important;color:#e8e1d5!important}html[data-be-dark=\"aan\"] body .summary-compact-metric.summary-compact-metric.summary-compact-metric.summary-compact-metric.summary-compact-metric.summary-compact-metric.summary-compact-metric.summary-compact-metric .summary-compact-label{color:#bfb6a5!important}html[data-be-dark=\"aan\"] body :is(nav,.sidebar,.sidenav){background:#211e1a!important}html[data-be-dark=\"aan\"] #be-panelen table tr:nth-child(odd){background:#37332c!important}html[data-be-dark=\"aan\"] #be-panelen table tr:nth-child(even){background:#454035!important}html[data-be-dark=\"aan\"] #be-panelen details{background:#373229!important}html[data-be-dark=\"aan\"] #be-panelen summary{color:#bfb6a5!important}html[data-be-dark=\"aan\"] #be-panelen details>div:first-of-type>div:nth-child(odd){background:#37332c!important}html[data-be-dark=\"aan\"] #be-panelen details>div:first-of-type>div:nth-child(even){background:#454035!important}html[data-be-dark=\"aan\"] body :is(nav,.sidebar,.sidenav) a{color:#e8e1d5!important}html[data-be-dark=\"aan\"] body :is(nav,.sidebar,.sidenav) a:hover{background:#514838!important;color:#f0e8da!important}html[data-be-dark=\"aan\"] body :is(nav,.sidebar,.sidenav) a:is(.active,[aria-current=\"page\"]),html[data-be-dark=\"aan\"] body :is(.nav-link.active,.btn-primary,.btn-check:checked+.btn,[aria-pressed=\"true\"],[aria-selected=\"true\"]){background:#8b734e!important;border-color:#ad946b!important;color:#fff3dd!important}html[data-be-dark=\"aan\"] body :is(.btn-light,.btn-outline-primary){background:#454036!important;border-color:#675e4f!important;color:#e8e1d5!important}';\n document.documentElement.appendChild(beDarkSheet);\n beDarkObserver=new MutationObserver(function(records){records.forEach(function(r){\n if(r.type==='attributes')beDarkQueue(r.target.parentElement||r.target);\n else r.addedNodes.forEach(beDarkQueue);\n });});\n beDarkObserver.observe(document.body,{subtree:true,childList:true,attributes:true,attributeFilter:['class','style']});\n window.addEventListener('resize',function(){beDarkQueue(document.body);});\n var bridge=document.createElement('script');bridge.textContent=\"(function(){\\n if(window.__beDarkCanvas)return;window.__beDarkCanvas=true;\\nfunction beDarkColor(value, kind) {\\n if (typeof value !== 'string') return value;\\n value=value.trim();\\n if(value==='white')value='rgb(255,255,255)';\\n if(value==='black')value='rgb(0,0,0)';\\n if(/^#[0-9a-f]{3,8}$/i.test(value)){\\n var hex=value.slice(1);if(hex.length===3||hex.length===4)hex=hex.split('').map(function(c){return c+c;}).join('');\\n value='rgba('+[0,2,4].map(function(i){return parseInt(hex.slice(i,i+2),16);}).join(',')+','+(hex.length===8?parseInt(hex.slice(6,8),16)/255:1)+')';\\n }\\n var srgb=value.match(/^color\\\\(srgb\\\\s+([\\\\d.]+)\\\\s+([\\\\d.]+)\\\\s+([\\\\d.]+)(?:\\\\s*\\\\/\\\\s*([\\\\d.]+))?\\\\)$/);\\n if(srgb)value='rgba('+[1,2,3].map(function(i){return Math.round(+srgb[i]*255);}).join(',')+','+(srgb[4]||1)+')';\\n var m=value.match(/^rgba?\\\\(\\\\s*([\\\\d.]+)[, ]+([\\\\d.]+)[, ]+([\\\\d.]+)(?:\\\\s*[,/]\\\\s*([\\\\d.]+))?\\\\s*\\\\)$/);\\n if(!m)return value;\\n var r=+m[1],g=+m[2],b=+m[3],a=m[4]==null?1:+m[4];\\n if(!a)return value;\\n var hi=Math.max(r,g,b),lo=Math.min(r,g,b),light=(r+g+b)/3;\\n var neutral=hi-lo<48;\\n // Data colours retain their identity; theme only neutral chart surfaces.\\n if(hi-lo>=48){\\n var data;\\n if(r>g*1.4&&r>b*1.2)data=g>75&&g>b*1.5?[236,165,36]:[242,79,91];\\n else if(g>r*1.15&&g>b*1.05)data=[51,192,113];\\n else if(b>r*1.15&&g>r*1.1)data=[49,173,222];\\n else if(r>b*.85&&b>r*.75&&b>g*1.12)data=[203,181,143];\\n if(data)return 'rgba('+data.join(',')+','+(kind==='background'?Math.max(a,.72):a)+')';\\n }\\n var out;\\n if(kind==='background') {\\n if(light>180)out=light>245?[55,50,43]:[69,64,54];\\n else if(neutral&&light>95)out=[78,71,60];\\n } else if(kind==='border') {\\n if(light>150)out=[103,94,79];\\n } else {\\n if(neutral)out=light<90?[232,225,213]:light<205?[191,182,165]:null;\\n else if(r>b*.85&&b>r*.75&&b>g*1.12){out=[203,181,143];}\\n else if(g>r*1.15&&g>b*1.05){out=[51,192,113];}\\n else if(r>g*1.4&&r>b*1.2){out=[242,79,91];}\\n else if(light<190) {\\n // Lift existing hue rather than replacing status/series colours.\\n var t=light<80?.55:.40;\\n out=[r,g,b].map(function(v){return Math.round(v+(255-v)*t);});\\n }\\n }\\n return out?'rgba('+out.join(',')+','+a+')':value;\\n }\\n function map(v,kind){\\n if(typeof v!=='string')return v;\\n if(/^#[0-9a-f]{6}$/i.test(v))v='rgb('+[1,3,5].map(function(i){return parseInt(v.slice(i,i+2),16);}).join(',')+')';\\n if(/^#[0-9a-f]{3}$/i.test(v))v='rgb('+[1,2,3].map(function(i){return parseInt(v[i]+v[i],16);}).join(',')+')';\\n return beDarkColor(v,kind);\\n }\\n var p=CanvasRenderingContext2D.prototype;\\n ['fill','fillRect','fillText','stroke','strokeRect','strokeText'].forEach(function(name){\\n var original=p[name];if(!original)return;\\n p[name]=function(){\\n if(document.documentElement.getAttribute('data-be-dark')!=='aan')return original.apply(this,arguments);\\n var prop=name.indexOf('stroke')===0?'strokeStyle':'fillStyle',old=this[prop];\\n var kind=name.indexOf('Text')!==-1?'text':name.indexOf('stroke')===0?(this.lineWidth<1.5?'border':'text'):'background';\\n this[prop]=map(old,kind);\\n try{return original.apply(this,arguments);}finally{this[prop]=old;}\\n };\\n });\\n document.addEventListener('be-dark-change',function(){\\n if(typeof Chart==='undefined'||!Chart.getChart)return;\\n document.querySelectorAll('canvas').forEach(function(cv){var c=Chart.getChart(cv);if(c)c.draw();});\\n });\\n})();\\n\";\n document.documentElement.appendChild(bridge);bridge.remove();\n beDarkApply(beDarkOn);\n }\n if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',beDarkInit,{once:true});else beDarkInit();\n\n var EB_BASIS = 0.09161;\n var BTW = 1.21;\n var EB = 0.11085;\n var OPSLAG = 0.02;\n\n var LEV_DAG = 0.2184;\n var TRA_DAG = 1.3861;\n var VER_DAG = 628.96 / 365;\n var VAST_DAG = VER_DAG - LEV_DAG - TRA_DAG;\n\n var LS = {\n voorschot: 'be_cfg_voorschot',\n startdatum: 'be_cfg_startdatum',\n saldering: 'be_cfg_saldering',\n balansBlok: 'be_cfg_balansblok',\n allin: 'be_cfg_allin_aan',\n actueelAllin: 'be_cfg_actueel_allin',\n voorlopigeDagen: 'be_provisional_days_v43',\n voorlopigeAudit: 'be_provisional_audit_v1'\n };\n\n var MND_KORT = [\n 'Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun',\n 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'\n ];\n\n var MND_LANG = [\n 'januari', 'februari', 'maart', 'april',\n 'mei', 'juni', 'juli', 'augustus',\n 'september', 'oktober', 'november', 'december'\n ];\n\n var D = {\n rand: '#e8e2f0',\n label: '#8c6fb5',\n paars: '#6B3FA0',\n paarsLicht: '#9b7ec8',\n rood: '#dc3545',\n groen: '#198754',\n oranje: '#e07b00',\n grijs: '#6c757d',\n inkt: '#2b2733',\n roodZacht: '#c8a0a0',\n groenZacht: '#90c8a0'\n };\n\n var cfg = {\n voorschot: parseFloat(\n localStorage.getItem(LS.voorschot) || '42.98'\n ),\n\n start: (function () {\n var s = localStorage.getItem(LS.startdatum);\n\n if (s) {\n var d = new Date(s);\n if (!isNaN(d)) return d;\n }\n\n return new Date('2025-11-04');\n })(),\n\n saldering:\n localStorage.getItem(LS.saldering) !== 'uit',\n\n balansBlok:\n localStorage.getItem(LS.balansBlok) !== 'uit',\n\n // Resultaten begint bij iedere paginalaad met de originele Balans-data.\n allin: localStorage.getItem(LS.allin) === 'aan'\n };\n\n var salderingPrognose = false;\n\n function n2(v) {\n return String(v).padStart(2, '0');\n }\n\n function iso(d) {\n return (\n d.getFullYear() +\n '-' +\n n2(d.getMonth() + 1) +\n '-' +\n n2(d.getDate())\n );\n }\n\n function datumPlusMaanden(d, aantal) {\n var jaar =\n d.getFullYear();\n\n var maand =\n d.getMonth() + aantal;\n\n var laatsteDag =\n new Date(\n jaar,\n maand + 1,\n 0\n ).getDate();\n\n return new Date(\n jaar,\n maand,\n Math.min(\n d.getDate(),\n laatsteDag\n )\n );\n }\n\n function datumDagNummer(d) {\n return Math.floor(\n Date.UTC(\n d.getFullYear(),\n d.getMonth(),\n d.getDate()\n ) /\n 86400000\n );\n }\n\n function contractVoortgang(start, nu) {\n var begin =\n new Date(\n start.getFullYear(),\n start.getMonth(),\n start.getDate()\n );\n\n var vandaag =\n new Date(\n nu.getFullYear(),\n nu.getMonth(),\n nu.getDate()\n );\n\n var grens =\n datumPlusMaanden(\n begin,\n 12\n );\n\n var totaalDagen =\n datumDagNummer(grens) -\n datumDagNummer(begin);\n\n var verstrekenDagen =\n Math.max(\n 0,\n Math.min(\n totaalDagen,\n datumDagNummer(vandaag) -\n datumDagNummer(begin)\n )\n );\n\n var maanden =\n (\n vandaag.getFullYear() -\n begin.getFullYear()\n ) *\n 12 +\n vandaag.getMonth() -\n begin.getMonth();\n\n maanden =\n Math.max(\n 0,\n Math.min(\n 12,\n maanden\n )\n );\n\n var maandAnker =\n datumPlusMaanden(\n begin,\n maanden\n );\n\n if (\n maanden > 0 &&\n datumDagNummer(maandAnker) >\n datumDagNummer(vandaag)\n ) {\n maanden--;\n maandAnker =\n datumPlusMaanden(\n begin,\n maanden\n );\n }\n\n var restDagen =\n vandaag < begin ||\n vandaag >= grens\n ? 0\n : Math.max(\n 0,\n datumDagNummer(vandaag) -\n datumDagNummer(maandAnker)\n );\n\n var pctExact =\n totaalDagen > 0\n ? verstrekenDagen /\n totaalDagen *\n 100\n : 0;\n\n return {\n grens: grens,\n totaalDagen: totaalDagen,\n verstrekenDagen: verstrekenDagen,\n contractDag:\n vandaag < begin\n ? 0\n : Math.min(\n totaalDagen,\n verstrekenDagen + 1\n ),\n maanden: maanden,\n restDagen: restDagen,\n pctExact: pctExact,\n gestart: vandaag >= begin,\n voltooid: vandaag >= grens\n };\n }\n\n // Alleen in deze pagina opnieuw gevalideerde resultaten mogen worden gebruikt.\n var voorlopigeSessie = String(Date.now()) + ':' + Math.random();\n\n function voorlopigeDagGrenzen(key) {\n if (!/^\\d{4}-\\d{2}-\\d{2}$/.test(String(key))) return null;\n var start = new Date(key + 'T00:00:00');\n if (!Number.isFinite(start.getTime()) || iso(start) !== key) return null;\n var eind = new Date(start.getTime());\n eind.setDate(eind.getDate() + 1);\n return { start: start.getTime(), eind: eind.getTime() };\n }\n\n function voorlopigeBerekeningGeldig(key, res, vanaf, tot) {\n var g = voorlopigeDagGrenzen(key);\n if (!g || !res || !(res.stukken > 0) || res.ongeldig) return false;\n if (vanaf !== g.start || tot <= vanaf || tot > g.eind || tot > Date.now()) return false;\n if (res.bronVanaf !== vanaf || res.bronTot !== tot) return false;\n if (!['importKwh', 'exportKwh', 'inkoopEur', 'verkoopEur', 'gedekt'].every(function (k) {\n return Number.isFinite(res[k]);\n }) || res.importKwh < 0 || res.exportKwh < 0) return false;\n // Datumzekerheid staat los van dekking: een aantoonbaar deelresultaat is geldig.\n return res.gedekt > 0 && res.gedekt <= tot - vanaf;\n\n }\n\n // Duurzaam bronarchief; geen automatische verwijdering van eerdere dagen.\n var beArchiefDb = null;\n var beArchiefFout = '';\n var beArchiefWachtrij = Promise.resolve();\n function beOpenArchief() {\n if (!beArchiefDb) beArchiefDb = new Promise(function (resolve, reject) {\n var req = indexedDB.open('balansenergie-bronnen-v1', 1);\n req.onupgradeneeded = function () { req.result.createObjectStore('dagen'); };\n req.onsuccess = function () { resolve(req.result); };\n req.onerror = function () { reject(req.error); };\n req.onblocked = function () { reject(new Error('Archief geblokkeerd')); };\n });\n return beArchiefDb;\n }\n async function beLeesBron(key) {\n var db = await beOpenArchief();\n return new Promise(function (resolve, reject) {\n var tx = db.transaction('dagen', 'readonly');\n var req = tx.objectStore('dagen').get(KLANT + ':' + key);\n req.onsuccess = function () { resolve(req.result || {}); };\n req.onerror = function () { reject(req.error); };\n });\n }\n async function beSchrijfBron(key, value) {\n var db = await beOpenArchief();\n return new Promise(function (resolve, reject) {\n var tx = db.transaction('dagen', 'readwrite');\n tx.objectStore('dagen').put(value, KLANT + ':' + key);\n tx.oncomplete = function () { resolve(); };\n tx.onerror = tx.onabort = function () { reject(tx.error || new Error('Opslaan mislukt')); };\n });\n }\n // Historical quotes are recovered from this browser’s own archive.\n var beLokalePrijsHistorie = {};\n function beHistorieMetingen(j) {\n var coarse = (j && j.rows || []).filter(function(r){return r && Number.isFinite(r.t) && Number.isFinite(r.po);});\n var fine = (j && j.priceSamples || []).filter(function(r){return r && Number.isFinite(r.x) && Number.isFinite(r.y);})\n .map(function(r){return {t:r.x,po:r.y,detail:2};});\n return beVoegMetingenSamen(coarse, fine);\n }\n function beVoegMetingenSamen(oud, nieuw) {\n var map = new Map();\n [].concat(oud || [], nieuw || []).forEach(function(r){\n if (!r || !Number.isFinite(r.t) || !Number.isFinite(r.po)) return;\n var old = map.get(r.t);\n if (!old || (r.detail || 1) >= (old.detail || 1))\n map.set(r.t, {t:r.t,po:r.po,detail:r.detail || 1});\n });\n var rows = Array.from(map.values()).sort(function(a,b){return a.t-b.t;});\n var fine = rows.filter(function(r){return r.detail === 2;}), intervals=[];\n for(var i=1;i tot) return;\n var key = iso(new Date(r.t));\n (nieuwPerDag[key] || (nieuwPerDag[key] = [])).push(r);\n });\n var dagen = await beLeesBron('index');\n var keys = dagen.keys || [];\n for (var key of Object.keys(nieuwPerDag)) {\n var oud = await beLeesBron(key);\n await beSchrijfBron(key, { rows: beVoegMetingenSamen(oud.rows, nieuwPerDag[key]) });\n if (keys.indexOf(key) === -1) keys.push(key);\n }\n await beSchrijfBron('index', { keys: keys.sort() });\n var samen = [];\n for (var key of keys) {\n var g = voorlopigeDagGrenzen(key);\n if (!g || g.eind < vanaf || g.start > tot) continue;\n var dag = await beLeesBron(key);\n samen = samen.concat((dag.rows || []).filter(function (r) { return r.t >= vanaf && r.t <= tot; }));\n }\n return { rows: samen.sort(function (a,b) { return a.t-b.t; }) };\n }\n async function beHaalBron(input, init) {\n var u = new URL(typeof input === 'string' ? input : input.href || input.url, location.origin);\n if (!/\\/customer\\/current-(history|prices)\\/api\\/$/.test(u.pathname)) return origFetch(input, init);\n var j = null;\n try {\n var resp = await origFetch(u.href, init);\n if (resp.ok) j = await resp.json();\n } catch (e) { /* Bij netwerkuitval blijft het archief leesbaar. */ }\n var werk = beArchiefWachtrij.then(function () { return beVerwerkBron(u, j); });\n beArchiefWachtrij = werk.catch(function () {});\n try {\n var data = await werk;\n beArchiefFout = '';\n return { ok: true, json: function () { return Promise.resolve(data); } };\n } catch (e) {\n beArchiefFout = 'Lokale opslag mislukt: ' + e.message;\n console.error('[BE archief]', beArchiefFout);\n return { ok: !!j, json: function () { return Promise.resolve(j); } };\n }\n }\n var beArchiefBezig = false;\n var beArchiefImportKlaar = false;\n async function beArchiefOnderhoud() {\n if (!KLANT || KLANT === 'account' || beArchiefBezig || document.hidden) return;\n var onderhoudKey='be_onderhoud_laatst:'+KLANT;\n if(Date.now()-Number(sessionStorage.getItem(onderhoudKey)||0)<300000)return;\n sessionStorage.setItem(onderhoudKey,String(Date.now()));\n beArchiefBezig = true;\n try {\n if (!beArchiefImportKlaar) {\n var migratie = await beLeesBron('console-import');\n var oudRapport = localStorage.getItem('be_brononderzoek_v1:' + KLANT);\n if (oudRapport) {\n var importData = JSON.parse(oudRapport);\n var prijsUrl = new URL('/customer/current-prices/api/', location.origin);\n var taak = beArchiefWachtrij.then(function () {\n return beVerwerkBron(prijsUrl, { prices: Object.values(importData.prijzen || {}), afrrEventIsps: importData.afrrEventIsps || [] });\n });\n beArchiefWachtrij = taak.catch(function () {});\n await taak;\n await beSchrijfBron('console-import', { klaar: true });\n }\n beArchiefImportKlaar = true;\n }\n var nu = Date.now();\n var u = new URL('/customer/current-history/api/', location.origin);\n u.searchParams.set('installation', KLANT);\n u.searchParams.set('start', String(nu - 26 * 3600000));\n u.searchParams.set('end', String(nu));\n var res = await Promise.all([\n beHaalBron(u.href, { credentials:'same-origin', cache:'no-store' }),\n beHaalBron(location.origin + '/customer/current-prices/api/', { credentials:'same-origin', cache:'no-store' })\n ]);\n var prijzen = await res[1].json();\n if (!prijzen) return;\n var rijPrijzen = prijzen.prices.map(actueelPrijsRij);\n var index = await beLeesBron('index');\n for (var key of index.keys || []) {\n var g = voorlopigeDagGrenzen(key), dag = await beLeesBron(key);\n var tot = Math.min(nu, g.eind);\n if (tot <= g.start) continue;\n var berekend = actueelProjectieBereken((dag.rows || []).map(function (r) { return {x:r.t,y:r.po}; }), rijPrijzen, g.start, tot, nu);\n bewaarVoorlopigeDag(key, berekend, g.start, tot);\n }\n if (laatste) plan();\n } catch (e) {\n beArchiefFout = 'Lokale opslag niet beschikbaar: ' + e.message;\n console.error('[BE archief]', beArchiefFout);\n } finally { beArchiefBezig = false; }\n var melding = document.getElementById('be-archief-status');\n if (beArchiefFout && !melding && document.body) {\n melding = document.createElement('div'); melding.id='be-archief-status';\n melding.style.cssText='padding:10px;background:#fff0d6;color:#743900';\n document.body.prepend(melding);\n }\n if (melding) { melding.textContent=beArchiefFout; melding.hidden=!beArchiefFout; }\n }\n\n function leesVoorlopigeDagen() {\n try {\n var o =\n JSON.parse(\n localStorage.getItem(\n LS.voorlopigeDagen + ':' + KLANT\n ) || '{}'\n );\n\n if (!o || typeof o !== 'object' || Array.isArray(o)) return {};\n Object.keys(o).forEach(function (k) {\n var r = o[k];\n if (!r || r.key !== k || r.archiefVersie !== 1 || r.klant !== KLANT) delete o[k];\n });\n return o;\n\n } catch (e) {\n return {};\n }\n }\n\n function schrijfVoorlopigeDagen(o) {\n try {\n localStorage.setItem(\n LS.voorlopigeDagen + ':' + KLANT,\n JSON.stringify(o || {})\n );\n } catch (e) {}\n }\n\n /*\n * Bewaar de laatste voorlopige berekening wanneer Balans dezelfde dag\n * definitief publiceert. Zo blijft een verschil achteraf controleerbaar;\n * de actieve voorlopige cache zelf kan daarna gewoon worden opgeruimd.\n */\n function bewaarVoorlopigeAudit(dagKey, voorlopig, definitief) {\n if (!voorlopig || !/^\\d{4}-\\d{2}-\\d{2}$/.test(String(dagKey || ''))) {\n return;\n }\n\n try {\n var sleutel =\n LS.voorlopigeAudit + ':' + KLANT;\n\n var audit = JSON.parse(\n localStorage.getItem(sleutel) || '{}'\n );\n\n if (!audit || typeof audit !== 'object' || Array.isArray(audit)) {\n audit = {};\n }\n\n audit[dagKey] = {\n klant: KLANT,\n key: dagKey,\n voorlopig: voorlopig,\n definitief: definitief || null,\n vervangenOp: Date.now()\n };\n\n localStorage.setItem(\n sleutel,\n JSON.stringify(audit)\n );\n } catch (e) {}\n }\n\n function bewaarVoorlopigeDag(\n dagKey,\n res,\n vanaf,\n tot\n ) {\n var opslag = leesVoorlopigeDagen();\n if (!voorlopigeBerekeningGeldig(dagKey, res, vanaf, tot)) {\n // Minder beschikbare bronnen wissen nooit een eerder bewezen resultaat.\n return false;\n }\n var eerder = opslag[dagKey];\n // Reject truncated measurement windows, not a reduced availability of prices.\n var gemetenMs = Number.isFinite(res.gemetenMs) ? res.gemetenMs : res.gedekt;\n if (eerder && ((Number.isFinite(eerder.gemetenTot) && tot < eerder.gemetenTot) ||\n (Number.isFinite(eerder.gemetenMs) && gemetenMs < eerder.gemetenMs))) return false;\n var nu = Date.now();\n var grenzen = voorlopigeDagGrenzen(dagKey);\n var volleDag = grenzen.eind - grenzen.start;\n\n opslag[dagKey] = {\n validatie: voorlopigeSessie,\n archiefVersie: 1,\n klant: KLANT,\n key:\n dagKey,\n\n voorlopig:\n true,\n\n deelresultaat: !!res.prijsOnvolledig || res.gedekt < volleDag * 0.995,\n prijsOnvolledig: !!res.prijsOnvolledig,\n gemetenMs: gemetenMs,\n geprijsdeImportKwh: Number.isFinite(res.geprijsdeImportKwh) ? res.geprijsdeImportKwh : res.importKwh,\n geprijsdeExportKwh: Number.isFinite(res.geprijsdeExportKwh) ? res.geprijsdeExportKwh : res.exportKwh,\n historischKwh: res.historischKwh || 0,\n\n imp:\n res.importKwh,\n\n exp:\n res.exportKwh,\n\n inkoop:\n res.inkoopEur,\n\n verkoop:\n res.verkoopEur,\n\n laden:\n 0,\n\n ontladen:\n 0,\n\n pv:\n 0,\n\n afrrImp:\n 0,\n\n afrrExp:\n 0,\n\n afrrVergoeding:\n 0,\n\n dealsAantal:\n 0,\n\n gedektMs:\n res.gedekt,\n\n volledigheid:\n Math.min(\n 100,\n Math.max(\n 0,\n 100 *\n res.gedekt /\n volleDag\n )\n ),\n\n gemetenVanaf:\n vanaf,\n\n gemetenTot:\n tot,\n\n bijgewerkt:\n nu\n };\n\n schrijfVoorlopigeDagen(\n opslag\n );\n return true;\n }\n\n function voorlopigeDagenVoorMaand(\n mk,\n echteRijen\n ) {\n if (\n !/^\\d{4}-\\d{2}$/.test(\n String(mk || '')\n )\n ) {\n return [];\n }\n\n var opslag =\n leesVoorlopigeDagen();\n\n var echt = {};\n var gewijzigd = false;\n\n (echteRijen || [])\n .forEach(\n function (r) {\n if (\n r && r.bronAanwezig !== false &&\n /^\\d{4}-\\d{2}-\\d{2}$/.test(\n String(r.key || '')\n )\n ) {\n echt[r.key] = r;\n\n }\n }\n );\n\n /*\n * Zodra Balans een datum zelf levert, wint die altijd.\n * De lokale voorlopige kopie kan dan meteen weg.\n */\n Object.keys(opslag)\n .forEach(\n function (k) {\n if (echt[k]) {\n bewaarVoorlopigeAudit(\n k,\n opslag[k],\n echt[k]\n );\n\n delete opslag[k];\n gewijzigd = true;\n }\n }\n );\n\n if (gewijzigd) {\n schrijfVoorlopigeDagen(\n opslag\n );\n }\n\n var vandaag =\n iso(new Date());\n\n return Object.keys(opslag)\n .filter(\n function (k) {\n var r =\n opslag[k];\n\n return (\n k.indexOf(\n mk + '-'\n ) === 0 &&\n k <= vandaag &&\n !echt[k] &&\n r &&\n r.voorlopig &&\n Number.isFinite(\n r.inkoop\n ) &&\n Number.isFinite(\n r.verkoop\n )\n );\n }\n )\n .sort()\n .map(\n function (k) {\n return opslag[k];\n }\n );\n }\n\n function voorlopigDagLabel(key) {\n var p =\n String(key || '')\n .split('-');\n\n if (p.length !== 3) {\n return '~';\n }\n\n var maand =\n MND_KORT[\n parseInt(p[1], 10) - 1\n ] || '';\n\n return (\n '~' +\n parseInt(p[2], 10) +\n ' ' +\n maand.toLowerCase()\n );\n }\n\n function voorlopigeDagenTekst(\n rijen,\n mk\n ) {\n var dagen = {};\n\n (rijen || [])\n .forEach(\n function (r) {\n if (\n !r ||\n !r.voorlopig ||\n String(r.key || '')\n .indexOf(\n mk + '-'\n ) !== 0\n ) {\n return;\n }\n\n var d =\n parseInt(\n String(r.key)\n .slice(8, 10),\n 10\n );\n\n if (d > 0) {\n dagen[d] = true;\n }\n }\n );\n\n var lijst =\n Object.keys(dagen)\n .map(\n function (d) {\n return parseInt(\n d,\n 10\n );\n }\n )\n .sort(\n function (a, b) {\n return a - b;\n }\n );\n\n if (!lijst.length) {\n return '';\n }\n\n if (lijst.length === 1) {\n return (\n lijst[0] +\n ' voorlopig'\n );\n }\n\n if (lijst.length === 2) {\n return (\n lijst[0] +\n ' en ' +\n lijst[1] +\n ' voorlopig'\n );\n }\n\n return (\n lijst\n .slice(0, -1)\n .join(', ') +\n ' en ' +\n lijst[\n lijst.length - 1\n ] +\n ' voorlopig'\n );\n }\n\n function getal(v) {\n return (\n typeof v === 'number' &&\n isFinite(v)\n )\n ? v\n : 0;\n }\n\n function eur(v, dec) {\n if (dec === undefined) dec = 2;\n\n return (\n '\\u20ac\\u00a0' +\n Math.abs(v).toLocaleString(\n 'nl-NL',\n {\n minimumFractionDigits: dec,\n maximumFractionDigits: dec\n }\n )\n );\n }\n\n function eurT(v, dec) {\n if (dec === undefined) dec = 2;\n\n return (\n (v < 0 ? '\\u2212' : '+') +\n '\\u00a0\\u20ac\\u00a0' +\n Math.abs(v).toLocaleString(\n 'nl-NL',\n {\n minimumFractionDigits: dec,\n maximumFractionDigits: dec\n }\n )\n );\n }\n\n function eurCent(v) {\n return (\n (v < 0 ? '\\u2212' : '+') +\n '\\u20ac' +\n Math.abs(v).toLocaleString(\n 'nl-NL',\n {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2\n }\n )\n );\n }\n\n function eurKort(v) {\n return (\n (v < 0 ? '\\u2212' : '+') +\n '\\u20ac' +\n Math.round(\n Math.abs(v)\n ).toLocaleString('nl-NL')\n );\n }\n\n function num(v, dec) {\n return getal(v).toLocaleString(\n 'nl-NL',\n {\n minimumFractionDigits: dec,\n maximumFractionDigits: dec\n }\n );\n }\n\n function kwh(v) {\n return num(v, 0) + ' kWh';\n }\n\n function ct(v) {\n return num(v * 100, 1) + ' ct';\n }\n\n function prijs(v) {\n return eur(v, 3) + ' / kWh';\n }\n\n function kleur(v) {\n return v < 0\n ? D.rood\n : D.groen;\n }\n\n function kleurZacht(v) {\n return v < 0\n ? D.roodZacht\n : D.groenZacht;\n }\n\n function maandLabel(mk) {\n var p = String(mk).split('-');\n\n return (\n MND_KORT[\n parseInt(p[1], 10) - 1\n ] +\n ' ' +\n p[0]\n );\n }\n\n function maandNaam(mk) {\n return MND_LANG[\n parseInt(\n String(mk).split('-')[1],\n 10\n ) - 1\n ];\n }\n\n function dagenInMaand(mk) {\n var p = String(mk).split('-');\n\n return new Date(\n parseInt(p[0], 10),\n parseInt(p[1], 10),\n 0\n ).getDate();\n }\n\n var dagenLopend = null;\n\n function haalLopendeDagen() {\n if (dagenLopend !== null) {\n return Promise.resolve(\n dagenLopend\n );\n }\n\n var mk =\n iso(new Date()).slice(0, 7);\n\n var u =\n new URL(\n location.origin +\n '/customer/' +\n KLANT +\n '/results-v2-api/'\n );\n\n u.searchParams.set(\n 'interval',\n 'month'\n );\n\n u.searchParams.set(\n 'period',\n mk\n );\n\n u.searchParams.set(\n 'include_vat',\n '1'\n );\n\n u.searchParams.set(\n 'include_delivery_cost',\n '1'\n );\n\n return origFetch(\n u,\n {\n credentials: 'same-origin'\n }\n )\n\n .then(function (r) {\n return r.ok\n ? r.json()\n : null;\n })\n\n .then(function (j) {\n dagenLopend =\n (\n j &&\n Array.isArray(j.series)\n )\n ? j.series.length\n : 0;\n\n return dagenLopend;\n })\n\n .catch(function () {\n dagenLopend = 0;\n return 0;\n });\n }\n\n var cacheMaandDagen = {};\n\n function haalMaandDagen(mk) {\n if (cacheMaandDagen[mk]) {\n return Promise.resolve(\n cacheMaandDagen[mk]\n );\n }\n\n var u =\n new URL(\n location.origin +\n '/customer/' +\n KLANT +\n '/results-v2-api/'\n );\n\n u.searchParams.set(\n 'interval',\n 'month'\n );\n\n u.searchParams.set(\n 'period',\n mk\n );\n\n u.searchParams.set(\n 'include_vat',\n '1'\n );\n\n u.searchParams.set(\n 'include_delivery_cost',\n '1'\n );\n\n return origFetch(\n u,\n {\n credentials: 'same-origin'\n }\n )\n\n .then(function (r) {\n return r.ok\n ? r.json()\n : null;\n })\n\n .then(function (j) {\n var pv =\n (j && j.pricing) || {};\n\n var rijen =\n (\n j &&\n Array.isArray(j.series)\n )\n ? j.series.map(\n function (s) {\n return naarIncl(\n s,\n pv\n );\n }\n )\n : [];\n\n cacheMaandDagen[mk] =\n rijen;\n\n return rijen;\n })\n\n .catch(function () {\n cacheMaandDagen[mk] = [];\n return [];\n });\n }\n\n function dagenVerstreken(mk) {\n var nu = new Date();\n\n var lopend =\n iso(nu).slice(0, 7);\n\n if (mk === lopend) {\n return dagenLopend !== null\n ? dagenLopend\n : nu.getDate();\n }\n\n if (mk > lopend) {\n return 0;\n }\n\n return dagenInMaand(mk);\n }\n\n function voorschotVoor(\n mk,\n dagen,\n geschat\n ) {\n if (\n geschat ||\n !mk ||\n !/^\\d{4}-\\d{2}$/.test(\n String(mk)\n )\n ) {\n return cfg.voorschot;\n }\n\n var totaal =\n dagenInMaand(mk);\n\n if (\n !(dagen > 0) ||\n dagen >= totaal\n ) {\n return cfg.voorschot;\n }\n\n return (\n cfg.voorschot *\n dagen /\n totaal\n );\n }\n\n function isLopendeMaand(mk) {\n return (\n mk ===\n iso(\n new Date()\n ).slice(0, 7)\n );\n }\n\n function naSaldering(mk) {\n return (\n parseInt(\n String(mk).slice(0, 4),\n 10\n ) >= 2027\n );\n }\n\n function contractMaanden() {\n var uit = [];\n\n for (\n var i = 0;\n i < 12;\n i++\n ) {\n var d =\n new Date(\n cfg.start.getFullYear(),\n cfg.start.getMonth() + i,\n 1\n );\n\n uit.push(\n d.getFullYear() +\n '-' +\n n2(\n d.getMonth() + 1\n )\n );\n }\n\n return uit;\n }\n\n function el(\n tag,\n css,\n html\n ) {\n var e =\n document.createElement(tag);\n\n if (css) {\n e.style.cssText = css;\n }\n\n if (html !== undefined) {\n e.innerHTML = html;\n }\n\n return e;\n }\n\n function kaart(\n id,\n titel,\n rechts\n ) {\n var c =\n el(\n 'div',\n 'background:#fff;' +\n 'border:1px solid ' +\n D.rand +\n ';border-radius:10px;' +\n 'padding:15px 18px 17px;' +\n 'margin-top:14px;' +\n 'box-shadow:0 1px 4px rgba(107,63,160,.06);'\n );\n\n c.id = id;\n\n if (titel) {\n c.appendChild(\n el(\n 'div',\n 'display:flex;' +\n 'align-items:center;' +\n 'justify-content:space-between;' +\n 'gap:12px;' +\n 'margin-bottom:13px;',\n '' +\n titel +\n '' +\n (rechts || '')\n )\n );\n }\n\n return c;\n }\n\n function sectie(\n id,\n titel,\n rechts\n ) {\n var c =\n el(\n 'div',\n 'margin-top:20px;' +\n 'padding-top:17px;' +\n 'border-top:1px solid ' +\n D.rand +\n ';'\n );\n\n c.id = id;\n\n if (titel) {\n c.appendChild(\n el(\n 'div',\n 'display:flex;' +\n 'align-items:center;' +\n 'justify-content:space-between;' +\n 'gap:12px;' +\n 'margin-bottom:13px;',\n '' +\n titel +\n '' +\n (rechts || '')\n )\n );\n }\n\n return c;\n }\n\n var klantMatch =\n location.pathname.match(\n /^\\/customer\\/([^/]+)(?:\\/|$)/\n );\n\n var KLANT =\n klantMatch\n ? klantMatch[1]\n : localStorage.getItem(\n 'be_laatste_installatie'\n );\n\n function geldigeKlant(id) {\n return (\n !!id &&\n !/^(?:account|results|current-history|current-prices|undefined|null)$/i.test(\n id\n )\n );\n }\n\n function onthoudKlant(id) {\n if (!geldigeKlant(id)) {\n return false;\n }\n\n var gewijzigd =\n KLANT !== id;\n\n KLANT = id;\n\n try {\n localStorage.setItem(\n 'be_laatste_installatie',\n id\n );\n } catch (e) {}\n\n return gewijzigd;\n }\n\n function klantUitUrl(waarde) {\n try {\n var u =\n new URL(\n waarde,\n location.origin\n );\n\n var installatie =\n u.searchParams.get(\n 'installation'\n );\n\n if (\n geldigeKlant(\n installatie\n )\n ) {\n return installatie;\n }\n\n var m =\n u.pathname.match(\n /^\\/customer\\/([^/]+)\\/(?:results|account)(?:\\/|$)/\n );\n\n return (\n m &&\n geldigeKlant(m[1])\n )\n ? m[1]\n : null;\n\n } catch (e) {\n return null;\n }\n }\n\n function verversKlantUitPad() {\n var m = location.pathname.match(\n /^\\/customer\\/([^/]+)(?:\\/|$)/\n );\n\n if (\n m &&\n geldigeKlant(m[1])\n ) {\n onthoudKlant(m[1]);\n\n return KLANT;\n }\n\n /*\n * Op de nieuwe Actueel-route staat het installatie-id niet meer in\n * location.pathname. De eigen history-aanvraag van het dashboard bevat\n * het id nog wel. Resource Timing werkt ook in Safari voor deze\n * same-origin-aanvraag en verandert niets aan de netwerklaag van Balans.\n */\n try {\n var bronnen =\n performance.getEntriesByType(\n 'resource'\n );\n\n for (\n var i = bronnen.length - 1;\n i >= 0;\n i--\n ) {\n if (\n String(bronnen[i].name).indexOf(\n '/customer/current-history/api/'\n ) === -1\n ) {\n continue;\n }\n\n var uitBron =\n klantUitUrl(\n bronnen[i].name\n );\n\n if (uitBron) {\n onthoudKlant(\n uitBron\n );\n\n return KLANT;\n }\n }\n } catch (e) {}\n\n /*\n * Vang ook de situatie af waarin Safari Resource Timing opschoont:\n * de navigatielinks naar Resultaten/Account dragen hetzelfde id.\n */\n var links =\n document.querySelectorAll(\n 'a[href*=\"/customer/\"]'\n );\n\n for (\n var j = 0;\n j < links.length;\n j++\n ) {\n var uitLink =\n klantUitUrl(\n links[j].href\n );\n\n if (uitLink) {\n onthoudKlant(\n uitLink\n );\n\n break;\n }\n }\n\n return KLANT;\n }\n\n var beNativeFetch = window.fetch.bind(window);\n var beApiPending = new Map(), beApiCache = new Map();\n function beApiFetch(input, init) {\n var u=new URL(typeof input==='string'?input:input.url||input.href,location.origin);\n var method=(init&&init.method)||input.method||'GET';\n if(method.toUpperCase()!=='GET'||u.origin!==location.origin||!u.pathname.includes('api'))return beNativeFetch(input,init);\n u.searchParams.sort();var key=u.href;\n var ttl=u.pathname.includes('current-prices')?15000:u.pathname.includes('current-history')?30000:60000;\n var now=Date.now(),cached=beApiCache.get(key);\n var diskKey='be_api_cache:'+key;\n if(!cached&&u.pathname.includes('results')){\n try{var saved=JSON.parse(sessionStorage.getItem(diskKey)||'null');\n if(saved&&now-saved.time300000)return;\n try{sessionStorage.setItem(diskKey,JSON.stringify({time:Date.now(),body:body}));}catch(e){}\n }).catch(function(){});\n if(r.ok){beApiCache.set(key,{time:Date.now(),response:r.clone()});if(beApiCache.size>40)beApiCache.delete(beApiCache.keys().next().value);}\n return r;\n }).finally(function(){beApiPending.delete(key);});\n beApiPending.set(key,request);\n return request.then(function(r){return r.clone();});\n }\n var origFetch = beApiFetch;\n\n\n /*\n * ============================================================\n * ACTUEEL\n * ============================================================\n */\n\n var ACTUEEL_SALDEERT =\n true;\n\n var actueelAan =\n localStorage.getItem(\n LS.actueelAllin\n ) !== 'uit';\n\n var actueelRijen = [];\n var actueelDagPunten = [];\n var actueelLaatstGeschreven = null;\n var actueelGestart = false;\n var actueelWasPagina = false;\n var actueelLaatsteKlant = null;\n var actueelHaalTimer = null;\n var actueelOnderhoudTimer = null;\n var actueelVoetnootTimer = null;\n \n function isActueelPagina() {\n return (\n /^\\/customer\\/?$/.test(\n location.pathname\n ) ||\n /^\\/customer\\/(?!account(?:\\/|$)|results(?:\\/|$))[^/]+\\/?$/.test(\n location.pathname\n )\n );\n }\n\n function actueelEuro(v) {\n return (\n '\\u20ac ' +\n v\n .toFixed(3)\n .replace(\n '.',\n ','\n )\n );\n }\n\n function actueelAllin(\n kaal,\n isExport\n ) {\n return isExport\n ? (\n kaal * BTW -\n OPSLAG +\n (\n ACTUEEL_SALDEERT\n ? EB\n : 0\n )\n )\n : (\n kaal * BTW +\n OPSLAG +\n EB\n );\n }\n\n var ACTUEEL_BRIDGE_CODE =\n \"(function(){\\n\" +\n \" if(window.__beActueelAllinBridge)return;\\n\" +\n \" window.__beActueelAllinBridge=true;\\n\" +\n \" var CANVAS='battery-chart';\\n\" +\n \" var st={rijen:[],aan:true,eb:0.11085,btw:1.21,opslag:0.02,saldeert:true};\\n\" +\n \" var ctxRef=null;\\n\" +\n \" function chart(){return (typeof Chart!=='undefined'&&Chart.getChart)?Chart.getChart(CANVAS):null;}\\n\" +\n \" function euro(v){return '\\\\u20ac '+v.toFixed(3).replace('.',',');}\\n\" +\n \" function allin(kaal,isExport){return isExport?kaal*st.btw-st.opslag+(st.saldeert?st.eb:0):kaal*st.btw+st.opslag+st.eb;}\\n\" +\n \" function wikkel(){\\n\" +\n \" var c=chart(); if(!c||!c.ctx)return;\\n\" +\n \" if(ctxRef===c.ctx&&c.ctx.__beAllinFillText)return;\\n\" +\n \" ctxRef=c.ctx;\\n\" +\n \" var basisFill=(c.ctx.__beAllinOrigFill||c.ctx.fillText).bind(c.ctx);\\n\" +\n \" c.ctx.__beAllinOrigFill=basisFill;\\n\" +\n \" c.ctx.fillText=function(tekst,x,y,mw){\\n\" +\n \" try{\\n\" +\n \" if(st.aan&&typeof tekst==='string'&&tekst.indexOf('\\\\u20ac')!==-1&&/(?:\\\\u2248|Import|Inkoop|Afname|Export|Teruglever|Injectie|ISP|Nu)/i.test(tekst)){\\n\" +\n \" var explicietExport=/(?:Export|Teruglever|Injectie)/i.test(tekst);\\n\" +\n \" var explicietImport=/(?:Import|Inkoop|Afname)/i.test(tekst);\\n\" +\n \" var isExport=explicietExport;\\n\" +\n \" var t=c.scales.x?c.scales.x.getValueForPixel(x):null;\\n\" +\n \" var m=tekst.match(/\\\\u20ac\\\\s*([+\\\\-\\\\u2212]?\\\\s*\\\\d+(?:[.,]\\\\d+)?)/);\\n\" +\n \" var kaal=m?parseFloat(m[1].replace(/\\\\s/g,'').replace('\\\\u2212','-').replace(',','.')):null;\\n\" +\n \" var rr=null;\\n\" +\n \" if(t!=null){\\n\" +\n \" for(var i=0;it){rr=st.rijen[i];break;}\\n\" +\n \" }\\n\" +\n \" }\\n\" +\n \" if(rr&&!explicietExport&&!explicietImport&&kaal!=null&&isFinite(kaal))isExport=Math.abs(kaal-rr.ke)=t.measureText(lab).width){\\n\" +\n \" t.fillText(lab,mid,a.top+11);\\n\" +\n \" }else if(ruimte>=24){\\n\" +\n \" t.font='600 8px system-ui,-apple-system,sans-serif';\\n\" +\n \" t.fillText(q.wacht?'wacht':(q.afrr?'aFRR':'onb.'),mid,a.top+11);\\n\" +\n \" }else if(ruimte>=8){\\n\" +\n \" t.save();t.translate(mid,a.top+4);t.rotate(-Math.PI/2);t.textAlign='right';\\n\" +\n \" t.fillText(lab,0,3);t.restore();\\n\" +\n \" }\\n\" +\n \" if(ruimte<24)return;\\n\" +\n \" if(q.res==null||Math.abs(q.res)<0.005)return;\\n\" +\n \" var bt=tgBedrag(q.res)+(q.deel?'\\\\u2026':'');\\n\" +\n \" t.font='700 12px system-ui,-apple-system,sans-serif';\\n\" +\n \" if(p2-p1mx)return;\\n\" +\n \" if(gezien[r.start])return; gezien[r.start]=true;\\n\" +\n \" kw.push(r);\\n\" +\n \" if(r.wacht)return;\\n\" +\n \" var live=r.start<=nu&&nu=r.start&&v.t<=nu;});\\n\" +\n \" if(samples.length){\\n\" +\n \" samples.forEach(function(v){\\n\" +\n \" imp.push({x:v.t,y:v.pi,beLive:true});\\n\" +\n \" exp.push({x:v.t,y:v.pe,beLive:true});\\n\" +\n \" });\\n\" +\n \" var last=samples[samples.length-1];\\n\" +\n \" imp.push({x:nu,y:last.pi,beLive:true});\\n\" +\n \" exp.push({x:nu,y:last.pe,beLive:true});\\n\" +\n \" }else{\\n\" +\n \" imp.push({x:nu,y:r.pi,beLive:true});\\n\" +\n \" exp.push({x:nu,y:r.pe,beLive:true});\\n\" +\n \" }\\n\" +\n \" }else{\\n\" +\n \" imp.push({x:r.start,y:r.pi,beLive:false},{x:r.end,y:r.pi,beLive:false});\\n\" +\n \" exp.push({x:r.start,y:r.pe,beLive:false},{x:r.end,y:r.pe,beLive:false});\\n\" +\n \" }\\n\" +\n \" });\\n\" +\n \" ch.data.datasets[0].data=imp; ch.data.datasets[1].data=exp;\\n\" +\n \" // Match both plot edges in screen coordinates, including differing canvas offsets.\\n\" +\n \" if(bv&&bv.chartArea&&bv.scales.x){\\n\" +\n \" var srcRect=bv.canvas.getBoundingClientRect(),dstRect=ch.canvas.getBoundingClientRect();\\n\" +\n \" var srcRatio=srcRect.width/bv.width,dstRatio=dstRect.width/ch.width;\\n\" +\n \" if(srcRatio>0&&dstRatio>0){\\n\" +\n \" var left=(srcRect.left+bv.chartArea.left*srcRatio-dstRect.left)/dstRatio;\\n\" +\n \" var right=(srcRect.left+bv.chartArea.right*srcRatio-dstRect.left)/dstRatio;\\n\" +\n \" ch.options.layout.padding={top:6,left:0,right:Math.max(0,ch.width-right)};\\n\" +\n \" ch.options.scales.y.afterFit=function(axis){axis.width=Math.max(0,left);};\\n\" +\n \" var ticks=bv.scales.x.ticks.map(function(t){return {value:t.value};});\\n\" +\n \" ch.options.scales.x.afterBuildTicks=function(axis){axis.ticks=ticks;};\\n\" +\n \" ch.options.scales.x.ticks.autoSkip=false;\\n\" +\n \" }\\n\" +\n \" }\\n\" +\n \" ch.$beKwartieren=kw; ch.update('none');\\n\" +\n \" }\\n\" +\n \" document.addEventListener('be-tarief-update',function(){\\n\" +\n \" var n=document.getElementById('be-tarief-data'); if(!n)return;\\n\" +\n \" try{TG=Object.assign(TG,JSON.parse(n.textContent||'{}'));tgVul();}\\n\" +\n \" catch(e){console.error('[BE tariefgrafiek bridge]',e);}\\n\" +\n \" });\\n\" +\n \" setInterval(function(){ if(TG.aan) tgVul(); },3000);\\n\" +\n \" setInterval(wikkel,3000);\\n\" +\n \" console.log('[BE Actueel all-in bridge] actief');\\n\" +\n \"})();\\n\";\n\n function actueelInstalleerBridge() {\n if (!isActueelPagina()) {\n return;\n }\n\n /*\n * De bridge hoeft maar één keer geïnstalleerd te worden.\n * Belangrijk: hier NIET opnieuw actueelStuur() aanroepen,\n * anders wordt de Chart.js-grafiek iedere onderhoudsronde\n * opnieuw gerenderd.\n */\n if (\n document.getElementById(\n 'be-actueel-allin-data'\n )\n ) {\n return;\n }\n\n var s =\n document.createElement(\n 'script'\n );\n\n s.id =\n 'be-actueel-allin-bridge';\n\n s.textContent =\n ACTUEEL_BRIDGE_CODE;\n\n (\n document.head ||\n document.documentElement\n ).appendChild(s);\n\n s.remove();\n }\n\n function actueelStuur() {\n if (!isActueelPagina()) {\n return;\n }\n\n var n =\n document.getElementById(\n 'be-actueel-allin-data'\n );\n\n if (!n) {\n n =\n document.createElement(\n 'script'\n );\n\n n.type =\n 'application/json';\n\n n.id =\n 'be-actueel-allin-data';\n\n document.documentElement\n .appendChild(n);\n }\n\n n.textContent =\n JSON.stringify({\n rijen:\n actueelRijen,\n\n aan:\n actueelAan,\n\n eb:\n EB,\n\n btw:\n BTW,\n\n opslag:\n OPSLAG,\n\n saldeert:\n ACTUEEL_SALDEERT\n });\n\n document.dispatchEvent(\n new Event(\n 'be-actueel-allin-update'\n )\n );\n }\n\n /* ──────────────────────────────────────────────────────────────────\n * Tariefgrafiek (v4.6.5)\n *\n * Een tweede grafiek tussen de vermogensgrafiek en de SOC-rij, met het\n * afname- en invoedtarief per kwartier. Dezelfde kwartiervakken, dezelfde\n * tijdas en hetzelfde witte tekenvlak als de grafiek erboven; per vak het\n * etiket onbalans of aFRR en het resultaat van dat kwartier in euro.\n *\n * Twee dingen die eerder fout gingen en hier bewust anders staan:\n *\n * - Het etiket komt uit afrrEventIsps, nooit uit een prijsvergelijking.\n * Bij gewone onbalans verschillen afname en invoeding ook, want\n * opregelen en afregelen zijn twee tarieven. Over 141 kwartieren zat\n * een test op \"de prijzen lopen uiteen\" er 69 keer naast.\n *\n * - Het bedrag komt uit actueelProjectieBereken, dezelfde motor als het\n * dagresultaat. Een eigen integratie met een gatgrens van twee minuten\n * haalde bijna de helft van de energie uit een kwartier; deze functie\n * gebruikt vijf minuten en interpoleert bovendien netjes.\n * ────────────────────────────────────────────────────────────────── */\n\n var TARIEF_SLEUTEL = 'be_cfg_tariefgrafiek';\n\n var tariefAan =\n localStorage.getItem(TARIEF_SLEUTEL) !== 'uit';\n\n function tariefZorgBlok() {\n if (!isActueelPagina()) {\n return null;\n }\n\n var bestaand =\n document.getElementById('be-tarief-row');\n\n if (bestaand && bestaand.parentElement) {\n return bestaand;\n }\n\n var soc =\n document.querySelector('.energy-chart-soc-row');\n\n if (!soc || !soc.parentElement) {\n return null;\n }\n\n var rij =\n document.createElement('div');\n\n rij.id = 'be-tarief-row';\n rij.className = 'energy-chart-soc-row';\n\n rij.innerHTML =\n '
' +\n '' +\n '
' +\n '

';\n\n soc.parentElement.insertBefore(rij, soc);\n\n return rij;\n }\n\n function tariefStuur() {\n if (!isActueelPagina()) {\n return;\n }\n\n var blok = tariefZorgBlok();\n\n if (blok) {\n blok.hidden = !tariefAan;\n }\n\n var n =\n document.getElementById('be-tarief-data');\n\n if (!n) {\n n = document.createElement('script');\n n.type = 'application/json';\n n.id = 'be-tarief-data';\n document.documentElement.appendChild(n);\n }\n\n n.textContent =\n JSON.stringify({\n rijen: tariefAan ? tariefKwartieren() : [],\n aan: tariefAan,\n allin: actueelAan\n });\n\n document.dispatchEvent(\n new Event('be-tarief-update')\n );\n\n tariefZetNoot();\n }\n\n /* Per kwartier de prijzen en het resultaat. Het resultaat loopt via\n actueelProjectieBereken zodat het exact dezelfde regels volgt als het\n dagbedrag in de bedieningsrij. */\n function tariefKwartieren() {\n var KW = 15 * 60 * 1000;\n var nu = Date.now();\n if (beLivePrijsRows && beLivePrijsRows.length) {\n beLivePrijsRows=beOverbrugPrijzen(beLivePrijsRows,nu);\n actueelRijen=beLivePrijsRows.map(actueelPrijsRij).filter(function(r){return Number.isFinite(r.ki)||Number.isFinite(r.ke);});\n }\n var uit = [];\n\n var bekend = {};\n\n actueelRijen.forEach(function (r) {\n bekend[r.start] = r;\n });\n\n /* Balans publiceert een kwartier pas twee a drie minuten na het begin.\n De lege kwartieren gaan toch mee, zodat het vak en het etiket\n \"prijs volgt\" er alvast staan. */\n var starts = Object.keys(bekend).map(Number);\n\n var laatste = Math.floor(nu / KW) * KW;\n\n if (starts.length) {\n var vroegste = Math.min.apply(null, starts);\n\n for (var k = vroegste; k <= laatste; k += KW) {\n if (!bekend[k]) {\n bekend[k] = { start: k, end: k + KW, ki: null, ke: null, afrr: false };\n }\n }\n }\n\n Object.keys(bekend).map(Number).sort(function (a, b) {\n return a - b;\n }).forEach(function (k) {\n var r = bekend[k];\n\n var heeftPrijs =\n Number.isFinite(r.ki) &&\n Number.isFinite(r.ke);\n\n var res = null;\n var deel = false;\n\n if (heeftPrijs && !r.carried && actueelDagPunten.length) {\n var b =\n actueelProjectieBereken(\n actueelDagPunten,\n [r],\n r.start,\n r.end,\n nu\n );\n\n if (b && b.stukken && !b.ongeldig) {\n res = b.bedrag;\n deel = b.gedekt < (r.end - r.start) * 0.97;\n }\n }\n\n uit.push({\n start: r.start,\n end: r.end,\n afrr: !!r.afrr,\n marktImport:r.marktImport, marktExport:r.marktExport,\n carried:!!r.carried, sourceStart:r.sourceStart,\n provisional:r.provisional, fresh:r.fresh, updatedAt:r.updatedAt,\n values:r.values,\n verloop:(r.verloop || []).map(function (v) {\n return { t:v.t, carried:!!v.carried,\n pi:actueelAan ? actueelAllin(v.ki, false) : v.ki,\n pe:actueelAan ? actueelAllin(v.ke, true) : v.ke };\n }),\n wacht: !heeftPrijs,\n res: res,\n deel: deel,\n\n pi: heeftPrijs\n ? (actueelAan ? actueelAllin(r.ki, false) : r.ki)\n : null,\n\n pe: heeftPrijs\n ? (actueelAan ? actueelAllin(r.ke, true) : r.ke)\n : null\n });\n });\n\n return uit;\n }\n\n function tariefZetNoot() {\n var n =\n document.getElementById('be-tarief-noot');\n\n if (!n) {\n return;\n }\n\n n.textContent = (actueelAan\n ? 'All-in, incl. btw, leverkosten en energiebelasting. '\n : 'Kale tarieven, excl. btw, leverkosten en energiebelasting. ') +\n 'Gestippeld = lopend kwartier. Details in de tooltip.';\n }\n\n /* De schakelaar hoort naast de All-in knop in de bedieningsrij. In de\n grafiek zelf zou hij mee verdwijnen zodra je hem uitzet. */\n function tariefZorgKnop() {\n if (!isActueelPagina()) {\n return;\n }\n\n var rij =\n document.getElementById('be-actueel-bedieningsrij') ||\n document.querySelector('.energy-chart-legend');\n\n if (!rij) {\n return;\n }\n\n var b =\n document.getElementById('be-tariefgrafiek-knop');\n\n if (!b) {\n b = document.createElement('button');\n b.type = 'button';\n b.id = 'be-tariefgrafiek-knop';\n\n b.style.cssText =\n 'appearance:none;-webkit-appearance:none;display:flex;' +\n 'align-items:center;gap:8px;border:0;border-left:1px solid #e8e2f0;' +\n 'background:transparent;padding:3px 14px;cursor:pointer;font:inherit;' +\n 'font-size:12px;color:#2b2733;white-space:nowrap;flex:0 0 auto;' +\n 'margin-left:auto;box-shadow:none;';\n\n b.innerHTML =\n 'Tariefgrafiek' +\n '' +\n '' +\n '' +\n '';\n\n b.addEventListener('click', function () {\n tariefAan = !tariefAan;\n\n try {\n localStorage.setItem(\n TARIEF_SLEUTEL,\n tariefAan ? 'aan' : 'uit'\n );\n } catch (e) {}\n\n tariefTekenKnop();\n tariefStuur();\n });\n }\n\n var allin =\n rij.querySelector('[data-be-actueel-allin]');\n\n if (allin) {\n if (b.nextElementSibling !== allin) {\n rij.insertBefore(b, allin);\n }\n } else if (b.parentElement !== rij) {\n rij.appendChild(b);\n }\n\n tariefTekenKnop();\n }\n\n function tariefTekenKnop() {\n var b =\n document.getElementById('be-tariefgrafiek-knop');\n\n if (!b) {\n return;\n }\n\n b.setAttribute(\n 'aria-pressed',\n tariefAan ? 'true' : 'false'\n );\n\n b.title =\n tariefAan\n ? 'Tariefgrafiek verbergen'\n : 'Tariefgrafiek tonen';\n\n var track = b.querySelector('[data-be-tg-track]');\n var thumb = b.querySelector('[data-be-tg-thumb]');\n var status = b.querySelector('[data-be-tg-status]');\n\n if (track) {\n track.style.background = tariefAan ? '#6B3FA0' : '#9ca3af';\n }\n\n if (thumb) {\n thumb.style.transform =\n tariefAan ? 'translateX(16px)' : 'translateX(0)';\n }\n\n if (status) {\n status.textContent = tariefAan ? 'AAN' : 'UIT';\n }\n }\n\n function actueelPasPrijzenToe(j) {\n if (\n !j ||\n !Array.isArray(\n j.prices\n )\n ) {\n return;\n }\n\n actueelRijen = j.prices.map(actueelPrijsRij).filter(function (r) {\n return Number.isFinite(r.ki) || Number.isFinite(r.ke);\n });\n\n actueelStuur();\n\n tariefStuur();\n\n actueelWerkSchemaBij();\n actueelProjectieToon();\n\n console.log(\n '[BE Actueel all-in]',\n actueelRijen.length,\n 'kwartieren, schakelaar',\n actueelAan\n ? 'aan'\n : 'uit'\n );\n }\n\n var beLivePrijsRows = [];\n var bePrijsHydratie = null;\n function beHerstelPrijsVerloop(){\n if(bePrijsHydratie)return;\n bePrijsHydratie=beLeesBron('prijzen').then(function(saved){\n beLivePrijsRows=beVoegPrijzenSamen(saved.rows||[],beLivePrijsRows);\n beLivePrijsRows=beOverbrugPrijzen(beLivePrijsRows,Date.now());\n if(isActueelPagina())actueelPasPrijzenToe({prices:beLivePrijsRows});\n }).catch(function(e){console.warn('[BE prijsarchief lezen]',e.message);});\n }\n function beOverbrugPrijzen(rows, nu) {\n var start=Math.floor(nu/900000)*900000;\n var current=rows.find(function(p){return p.start===start;});\n var prior=rows.filter(function(p){return p.end===start;}).pop();\n if(!prior)return rows;\n var previous=actueelPrijsRij(prior),r=current&&actueelPrijsRij(current);\n if(!Number.isFinite(previous.ki)||!Number.isFinite(previous.ke))return rows;\n var seed={t:start,ki:previous.ki,ke:previous.ke,marktImport:previous.marktImport,marktExport:previous.marktExport,carried:true};\n var history=(current&¤t.verloop||[]).slice();\n if(!history.length||history[0].t>start)history.unshift(seed);\n var usable=r&&Number.isFinite(r.ki)&&Number.isFinite(r.ke);\n var replacement=usable?Object.assign({},current,{verloop:history}):\n Object.assign({},prior,{start:start,end:start+900000,provisional:true,carried:true,sourceStart:prior.start,verloop:history});\n return rows.filter(function(p){return p.start!==start;}).concat([replacement]).sort(function(a,b){return a.start-b.start;});\n }\n function beOntvangLivePrijzen(j) {\n if(!j||!Array.isArray(j.prices))return;\n beLivePrijsRows=beOverbrugPrijzen(beLivePrijsRows,Date.now());\n beLivePrijsRows=beVoegPrijzenSamen(beLivePrijsRows,j.prices,j.afrrEventIsps,j.afrrDiscovery);\n beLivePrijsRows=beOverbrugPrijzen(beLivePrijsRows,Date.now());\n actueelPasPrijzenToe({prices:beLivePrijsRows});\n }\n function beBewaarPrijzenLater(j) {\n var werk=beArchiefWachtrij.then(function(){return beVerwerkBron(new URL('/customer/current-prices/api/',location.origin),j);});\n beArchiefWachtrij=werk.catch(function(e){console.warn('[BE prijsarchief]',e.message);});\n }\n var actueelPrijsBezig = false;\n function actueelHaal() {\n if (\n !isActueelPagina()\n ) {\n return Promise.resolve();\n }\n\n if (actueelPrijsBezig) return Promise.resolve();\n actueelPrijsBezig = true;\n beHerstelPrijsVerloop();\n var controller = new AbortController();\n var timeout = setTimeout(function () { controller.abort(); }, 12000);\n return origFetch(\n location.origin +\n '/customer/current-prices/api/',\n {\n credentials:\n 'same-origin',\n\n signal: controller.signal,\n cache:\n 'no-store'\n }\n )\n\n .then(function (r) {\n return r.ok\n ? r.json()\n : null;\n })\n\n .then(function(j){beOntvangLivePrijzen(j);if(j)beBewaarPrijzenLater(j);})\n\n .catch(\n function (e) {\n console.warn(\n '[BE Actueel all-in] ophalen mislukt:',\n e.message\n );\n }\n ).finally(function () {\n clearTimeout(timeout);\n actueelPrijsBezig = false;\n });\n }\n\n function actueelHaalDagPunten() {\n if (\n !isActueelPagina() ||\n !KLANT\n ) {\n return Promise.resolve();\n }\n\n var beginVandaag =\n new Date();\n\n beginVandaag.setHours(\n 0,\n 0,\n 0,\n 0\n );\n\n var u =\n new URL(\n location.origin +\n '/customer/current-history/api/'\n );\n\n u.searchParams.set(\n 'installation',\n KLANT\n );\n\n u.searchParams.set(\n 'start',\n String(\n beginVandaag.getTime()\n )\n );\n\n u.searchParams.set(\n 'end',\n String(\n Date.now()\n )\n );\n\n return beHaalBron(\n u,\n {\n credentials:\n 'same-origin',\n\n cache:\n 'no-store'\n }\n )\n\n .then(function (r) {\n return r.ok\n ? r.json()\n : null;\n })\n\n .then(function (j) {\n if (\n !j ||\n !Array.isArray(\n j.rows\n )\n ) {\n return;\n }\n\n actueelDagPunten =\n j.rows\n .filter(\n function (r) {\n return (\n r &&\n Number.isFinite(r.t) &&\n Number.isFinite(r.po)\n );\n }\n )\n .map(\n function (r) {\n return {\n x:\n r.t,\n\n y:\n r.po\n };\n }\n )\n .sort(\n function (a, b) {\n return a.x - b.x;\n }\n );\n\n actueelProjectieToon();\n\n tariefStuur();\n })\n\n .catch(\n function (e) {\n console.warn(\n '[BE Actueel] daghistorie ophalen mislukt:',\n e.message\n );\n }\n );\n }\n \n function actueelHuidigeRij() {\n var nu =\n Date.now();\n\n for (\n var i = 0;\n i < actueelRijen.length;\n i++\n ) {\n if (\n actueelRijen[i].start <= nu &&\n actueelRijen[i].end > nu\n ) {\n return actueelRijen[i];\n }\n }\n\n return null;\n }\n\n function actueelWerkSchemaBij() {\n if (!isActueelPagina()) {\n return;\n }\n\n var e =\n document.getElementById(\n 'flow-grid-price'\n );\n\n if (!e) {\n return;\n }\n\n var t =\n e.textContent || '';\n\n if (!actueelAan) {\n var orig =\n e.getAttribute(\n 'data-be-actueel-orig'\n );\n\n if (\n orig &&\n t ===\n actueelLaatstGeschreven\n ) {\n e.textContent =\n orig;\n }\n\n actueelLaatstGeschreven =\n null;\n\n e.removeAttribute(\n 'data-be-actueel-orig'\n );\n\n return;\n }\n\n if (\n t.indexOf('\\u2248') === -1 ||\n t.indexOf('\\u20ac') === -1\n ) {\n return;\n }\n\n if (\n t ===\n actueelLaatstGeschreven\n ) {\n return;\n }\n\n var isExport =\n /(?:Export|Teruglever|Injectie)/i.test(t);\n\n /*\n * Reken primair vanuit de kale prijs die Balans werkelijk toont.\n * Een ISP-label kan kortstondig bij een andere prijsregel horen dan\n * Date.now(); de zichtbare bronwaarde voorkomt dan een afwijking.\n */\n var m =\n t.match(\n /\\u20ac\\s*([+\\-\\u2212]?\\s*\\d+(?:[.,]\\d+)?)/\n );\n\n var kaal =\n m\n ? parseFloat(\n m[1]\n .replace(/\\s/g, '')\n .replace('\\u2212', '-')\n .replace(',', '.')\n )\n : null;\n\n if (\n kaal == null ||\n !isFinite(kaal)\n ) {\n var r =\n actueelHuidigeRij();\n\n kaal =\n r\n ? (\n isExport\n ? r.ke\n : r.ki\n )\n : null;\n }\n\n if (\n kaal == null ||\n !isFinite(kaal)\n ) {\n return;\n }\n\n e.setAttribute(\n 'data-be-actueel-orig',\n t\n );\n\n var nieuw =\n t.replace(\n /\\u20ac\\s*[+\\-\\u2212]?\\s*\\d+(?:[.,]\\d+)?/,\n actueelEuro(\n actueelAllin(\n kaal,\n isExport\n )\n )\n );\n\n if (nieuw === t) {\n return;\n }\n\n e.textContent =\n nieuw;\n\n actueelLaatstGeschreven =\n nieuw;\n }\n\n function actueelVolgSchema() {\n if (!isActueelPagina()) {\n return;\n }\n\n var e =\n document.getElementById(\n 'flow-grid-price'\n );\n\n if (\n !e ||\n e.getAttribute(\n 'data-be-actueel-volgt'\n ) === '1'\n ) {\n return;\n }\n\n e.setAttribute(\n 'data-be-actueel-volgt',\n '1'\n );\n\n new MutationObserver(\n function () {\n actueelWerkSchemaBij();\n }\n ).observe(\n e,\n {\n childList: true,\n characterData: true,\n subtree: true\n }\n );\n }\n\n function actueelLegenda() {\n if (!isActueelPagina()) {\n return null;\n }\n\n var legenda =\n document.querySelector(\n '.energy-chart-legend'\n );\n\n var fallback =\n document.getElementById(\n 'be-actueel-bedieningshost'\n );\n\n if (legenda) {\n if (\n fallback &&\n !fallback.children.length\n ) {\n fallback.remove();\n }\n\n return legenda;\n }\n\n /*\n * Bij een verse landing ontbreekt de Balans-legenda in mobiele Safari\n * soms geheel. Maak dan direct een neutrale host buiten de grafiek.\n * Zodra Balans zijn eigen legenda toevoegt, verhuist de rij daarheen.\n */\n var canvas =\n document.getElementById(\n 'battery-chart'\n );\n\n if (!canvas || !canvas.parentElement) {\n return null;\n }\n\n if (!fallback) {\n fallback =\n document.createElement(\n 'div'\n );\n\n fallback.id =\n 'be-actueel-bedieningshost';\n\n fallback.style.cssText =\n 'display:block;' +\n 'width:100%;' +\n 'box-sizing:border-box;';\n\n var grafiekHost =\n canvas.parentElement;\n\n if (grafiekHost.parentElement) {\n grafiekHost.parentElement.insertBefore(\n fallback,\n grafiekHost.nextSibling\n );\n\n } else {\n grafiekHost.appendChild(\n fallback\n );\n }\n }\n\n return fallback;\n }\n\n function actueelVoegMobieleStijlToe() {\n if (\n document.getElementById(\n 'be-actueel-mobiel-css'\n )\n ) {\n return;\n }\n\n var stijl =\n document.createElement(\n 'style'\n );\n\n stijl.id =\n 'be-actueel-mobiel-css';\n\n stijl.textContent =\n '@media (max-width: 700px) and (orientation: portrait){' +\n '#be-actueel-bedieningsrij{' +\n 'display:flex!important;' +\n 'flex-wrap:wrap!important;' +\n 'align-items:stretch!important;' +\n 'gap:8px!important;' +\n '}' +\n '#be-actueel-resultaat{' +\n 'display:flex!important;' +\n 'flex:1 1 100%!important;' +\n 'width:100%!important;' +\n 'min-width:0!important;' +\n 'white-space:normal!important;' +\n 'flex-wrap:wrap!important;' +\n 'gap:6px 10px!important;' +\n '}' +\n '#be-actueel-resultaat>span:last-child{' +\n 'flex:1 1 100%!important;' +\n '}' +\n '.be-actueel-toggle{' +\n 'width:100%!important;' +\n 'margin-left:0!important;' +\n 'padding:9px 2px 1px!important;' +\n 'border-left:0!important;' +\n 'border-top:1px solid ' + D.rand + '!important;' +\n 'justify-content:flex-end!important;' +\n '}' +\n '}';\n\n (\n document.head ||\n document.documentElement\n ).appendChild(\n stijl\n );\n }\n\n function actueelZorgBedieningsRij() {\n if (!isActueelPagina()) {\n return null;\n }\n\n var legenda =\n actueelLegenda();\n\n if (!legenda) {\n return null;\n }\n\n var rij =\n document.getElementById(\n 'be-actueel-bedieningsrij'\n );\n\n if (!rij) {\n rij =\n document.createElement(\n 'div'\n );\n\n rij.id =\n 'be-actueel-bedieningsrij';\n }\n\n rij.style.cssText =\n 'display:flex;' +\n 'align-items:center;' +\n 'width:100%;' +\n 'box-sizing:border-box;' +\n 'gap:16px;' +\n 'margin-top:10px;' +\n 'padding:9px 12px;' +\n 'min-height:42px;' +\n 'border:1px solid ' +\n D.rand +\n ';border-radius:10px;' +\n 'background:#fff;' +\n 'box-shadow:0 1px 4px rgba(107,63,160,.05);' +\n 'font-size:12px;' +\n 'line-height:1.2;' +\n 'color:' +\n D.inkt +\n ';flex:0 0 100%;' +\n 'grid-column:1 / -1;';\n\n /*\n * Gebruik het BESTAANDE resultaat als dat er al is.\n * Zo ontstaan geen twee elementen met hetzelfde ID.\n */\n var resultaten =\n document.querySelectorAll(\n '#be-actueel-resultaat'\n );\n\n var info =\n resultaten.length\n ? resultaten[0]\n : null;\n\n /*\n * Eventuele dubbele oude resultaat-elementen opruimen.\n */\n for (\n var i = 1;\n i < resultaten.length;\n i++\n ) {\n resultaten[i].remove();\n }\n\n if (!info) {\n info =\n document.createElement(\n 'div'\n );\n\n info.id =\n 'be-actueel-resultaat';\n\n info.setAttribute(\n 'data-be-vandaag-info',\n '1'\n );\n }\n\n info.style.cssText =\n 'display:flex;' +\n 'align-items:center;' +\n 'gap:11px;' +\n 'white-space:nowrap;' +\n 'min-width:0;' +\n 'flex:1 1 auto;' +\n 'font-variant-numeric:tabular-nums;';\n\n /*\n * Resultaat MOET links IN onze balk staan.\n */\n if (\n info.parentElement !==\n rij\n ) {\n rij.insertBefore(\n info,\n rij.firstChild\n );\n }\n\n /*\n * Balk MOET als volledige regel IN de Balans-legenda staan.\n */\n if (\n rij.parentElement !==\n legenda\n ) {\n legenda.appendChild(\n rij\n );\n }\n\n return rij;\n }\n \n function actueelTekenSchakelaar(b) {\n b =\n b ||\n document.querySelector(\n '[data-be-actueel-allin]'\n );\n\n if (!b) {\n return;\n }\n\n b.setAttribute(\n 'aria-pressed',\n actueelAan\n ? 'true'\n : 'false'\n );\n\n b.title =\n actueelAan\n ? 'All-in prijzen uitschakelen'\n : 'All-in prijzen inschakelen';\n\n var track =\n b.querySelector(\n '[data-be-track]'\n );\n\n var thumb =\n b.querySelector(\n '[data-be-thumb]'\n );\n\n var status =\n b.querySelector(\n '[data-be-status]'\n );\n\n if (track) {\n track.style.background =\n actueelAan\n ? D.paars\n : '#9ca3af';\n }\n\n if (thumb) {\n thumb.style.transform =\n actueelAan\n ? 'translateX(16px)'\n : 'translateX(0)';\n }\n\n if (status) {\n status.textContent =\n actueelAan\n ? 'AAN'\n : 'UIT';\n }\n }\n\n function actueelZetAan(\n nieuweStand,\n b\n ) {\n actueelAan =\n !!nieuweStand;\n\n localStorage.setItem(\n LS.actueelAllin,\n actueelAan\n ? 'aan'\n : 'uit'\n );\n\n actueelTekenSchakelaar(\n b\n );\n\n actueelStuur();\n\n /* De tariefgrafiek toont dezelfde prijzen, dus die kantelt mee. */\n tariefStuur();\n\n actueelWerkSchemaBij();\n\n actueelZetVoetnoot();\n actueelProjectieToon();\n\n if (actueelAan) {\n actueelHaal();\n }\n\n console.log(\n '[BE Actueel all-in] plugin',\n actueelAan\n ? 'aan'\n : 'uit'\n );\n }\n\n function actueelBouwSchakelaar() {\n var rij =\n actueelZorgBedieningsRij();\n\n if (!rij) {\n return;\n }\n\n var b =\n document.querySelector(\n '[data-be-actueel-allin]'\n );\n\n if (!b) {\n b =\n document.createElement(\n 'button'\n );\n\n b.type =\n 'button';\n\n b.dataset.beActueelAllin =\n '1';\n\n b.className =\n 'be-actueel-toggle';\n\n b.innerHTML =\n 'All-in prijzen' +\n\n '' +\n\n '' +\n '' +\n\n '' +\n\n '' +\n '';\n\n b.addEventListener(\n 'click',\n function () {\n actueelZetAan(\n !actueelAan,\n b\n );\n }\n );\n }\n\n b.className =\n 'be-actueel-toggle';\n\n b.style.cssText =\n 'appearance:none;' +\n '-webkit-appearance:none;' +\n 'display:flex;' +\n 'align-items:center;' +\n 'gap:8px;' +\n 'width:auto;' +\n 'height:auto;' +\n 'cursor:pointer;' +\n 'border:0;' +\n 'border-left:1px solid ' +\n D.rand +\n ';background:transparent;' +\n 'padding:3px 2px 3px 14px;' +\n 'margin-left:auto;' +\n 'flex:0 0 auto;' +\n 'white-space:nowrap;' +\n 'color:' +\n D.inkt +\n ';font:inherit;' +\n 'box-shadow:none;';\n\n /*\n * Toggle hoort ALTIJD rechts in onze eigen rij.\n *\n * Normaal gebeurt hier niets.\n * Alleen een foutief geplaatste/oude toggle wordt één keer\n * naar de juiste plek verplaatst.\n */\n if (\n b.parentElement !==\n rij\n ) {\n rij.appendChild(\n b\n );\n }\n\n actueelTekenSchakelaar(\n b\n );\n }\n\n var ACTUEEL_NOOT_ALLIN =\n 'All-in prijzen per kWh: ' +\n 'kale prijs × 1,21, ' +\n 'plus € 0,02 leverkosten en ' +\n '€ 0,11085 energiebelasting incl. btw. ' +\n 'Lopende kwartieren (ISP) zijn een schatting ' +\n 'en worden ongeveer twee minuten na afloop vastgesteld.';\n\n var ACTUEEL_NOOT_KAAL =\n 'Kale prijzen per kWh: ' +\n 'actuele onbalans-/aFRR-prijs zonder btw, ' +\n 'leverkosten en energiebelasting. ' +\n 'Lopende kwartieren (ISP) zijn een schatting ' +\n 'en worden ongeveer twee minuten na afloop vastgesteld.';\n\n function actueelZetVoetnoot() {\n if (!isActueelPagina()) {\n return;\n }\n\n var n =\n document.querySelector(\n 'p.current-price-note'\n );\n\n if (!n) {\n return;\n }\n\n var tekst =\n actueelAan\n ? ACTUEEL_NOOT_ALLIN\n : ACTUEEL_NOOT_KAAL;\n\n if (\n n.textContent !==\n tekst\n ) {\n n.textContent =\n tekst;\n }\n\n n.style.color =\n actueelAan\n ? D.paars\n : D.grijs;\n }\n\n function actueelVolgVoetnoot() {\n if (actueelVoetnootTimer) {\n return;\n }\n\n /*\n * Geen MutationObserver op document.body:\n * Balans en de plugin kunnen anders elkaars tekstwijzigingen\n * direct blijven terugschrijven en zo de pagina blokkeren.\n * Een rustige timer houdt de voetnoot wel netjes actueel.\n */\n actueelVoetnootTimer =\n setInterval(\n function () {\n if (isActueelPagina()) {\n actueelZetVoetnoot();\n }\n },\n 500\n );\n }\n\n \n /* Actueel: voorlopig variabel stroomresultaat over de grafiekperiode.\n * Netaansluiting is gemeten in W; positief = import, negatief = export.\n * Integreer lineair tussen metingen, gesplitst op prijsgrenzen en nul.\n * Geen extrapolatie voorbij de laatste meting; hiaten blijven zichtbaar.\n */\n function actueelProjectieBereken(punten, prijzen, vanaf, tot, nu) {\n var uit = { bedrag: 0, importKwh: 0, exportKwh: 0, gedekt: 0,\n inkoopEur: 0, verkoopEur: 0,\n ouder: 0, recent: 0, huidig: 0, stukken: 0,\n bronVanaf: vanaf, bronTot: tot, gaten: 0, ongeldig: false };\n var kwartier = 15 * 60 * 1000;\n var huidigStart = Math.floor(nu / kwartier) * kwartier;\n var reeks = punten.filter(function (p) {\n return p && Number.isFinite(p.x) && Number.isFinite(p.y);\n }).slice().sort(function (a, b) { return a.x - b.x; });\n // Meter totals are independent of tariff availability. Split sign changes\n // before integrating, so simultaneous daily import/export never cancel out.\n var gemetenImport = 0, gemetenExport = 0, gemetenMs = 0;\n for (var mi = 1; mi < reeks.length; mi++) {\n var ma = reeks[mi - 1], mb = reeks[mi], md = mb.x - ma.x;\n if (md <= 0 || md > 300000) continue;\n var ml = Math.max(ma.x, vanaf), mr = Math.min(mb.x, tot, nu);\n if (mr <= ml) continue;\n gemetenMs += mr - ml;\n var mc = [ml, mr];\n if (ma.y * mb.y < 0) {\n var mz = ma.x + md * (-ma.y) / (mb.y - ma.y);\n if (mz > ml && mz < mr) mc.splice(1, 0, mz);\n }\n for (var mk = 1; mk < mc.length; mk++) {\n var mx = mc[mk - 1], my = mc[mk];\n var mw1 = ma.y + (mb.y - ma.y) * (mx - ma.x) / md;\n var mw2 = ma.y + (mb.y - ma.y) * (my - ma.x) / md;\n var me = (mw1 + mw2) / 2 * (my - mx) / 3600000000;\n gemetenImport += Math.max(0, me);\n gemetenExport += Math.max(0, -me);\n }\n }\n var ps = prijzen.filter(function (p) {\n return Number.isFinite(p.start) && Number.isFinite(p.end) && p.end > p.start;\n }).slice().sort(function (a, b) { return a.start - b.start; });\n for (var z = 1; z < ps.length; z++) {\n if (ps[z].start < ps[z - 1].end && ps[z].start < tot && ps[z - 1].end > vanaf) {\n uit.ongeldig = true;\n return uit;\n }\n }\n var gedektTot = null;\n var pi = 0;\n for (var i = 1; i < reeks.length; i++) {\n var a = reeks[i - 1], b = reeks[i];\n var duur = b.x - a.x;\n if (duur === 0 && a.y !== b.y && a.x >= vanaf && a.x < tot) uit.ongeldig = true;\n if (duur <= 0 || duur > 5 * 60 * 1000) continue;\n var links = Math.max(a.x, vanaf), rechts = Math.min(b.x, tot, nu);\n if (rechts <= links) continue;\n while (pi < ps.length && ps[pi].end <= links) pi++;\n for (var j = pi; j < ps.length && ps[j].start < rechts; j++) {\n var prijsRij = ps[j];\n var l = Math.max(links, prijsRij.start), r = Math.min(rechts, prijsRij.end);\n if (r <= l) continue;\n var grenzen = [l, r];\n if (a.y * b.y < 0) {\n var nul = a.x + duur * (-a.y) / (b.y - a.y);\n if (nul > l && nul < r) grenzen.push(nul);\n }\n for (var q = (Math.floor(l / kwartier) + 1) * kwartier; q < r; q += kwartier) {\n grenzen.push(q);\n }\n grenzen.sort(function (x, y) { return x - y; });\n for (var k = 1; k < grenzen.length; k++) {\n var x = grenzen[k - 1], y = grenzen[k];\n var w1 = a.y + (b.y - a.y) * (x - a.x) / duur;\n var w2 = a.y + (b.y - a.y) * (y - a.x) / duur;\n var energie = ((w1 + w2) / 2) * (y - x) / 3600000000;\n var exp = energie < 0;\n var kaal = exp ? prijsRij.ke : prijsRij.ki;\n if (!Number.isFinite(kaal)) continue;\n if(exp ? prijsRij.historischExport : prijsRij.historischImport)\n uit.historischKwh=(uit.historischKwh||0)+Math.abs(energie);\n var balansPrijs =\n exp\n ? (\n kaal * BTW -\n OPSLAG\n )\n : (\n kaal * BTW +\n OPSLAG\n );\n\n if (exp) {\n uit.verkoopEur +=\n -energie *\n balansPrijs;\n\n } else {\n uit.inkoopEur +=\n energie *\n balansPrijs;\n }\n\n var prijsVoorResultaat =\n actueelAan\n ? actueelAllin(\n kaal,\n exp\n )\n : kaal;\n\n var bedrag =\n -energie *\n prijsVoorResultaat;\n var slot = Math.floor(x / kwartier) * kwartier;\n var groep = slot >= huidigStart ? 'huidig'\n : slot + kwartier <= nu - 20 * 60 * 1000 ? 'ouder' : 'recent';\n uit[groep] += bedrag;\n uit.bedrag += bedrag;\n uit.importKwh += Math.max(0, energie);\n uit.exportKwh += Math.max(0, -energie);\n if (gedektTot !== null && x > gedektTot) uit.gaten++;\n gedektTot = y;\n uit.gedekt += y - x;\n uit.stukken++;\n }\n }\n }\n uit.geprijsdeImportKwh = uit.importKwh;\n uit.geprijsdeExportKwh = uit.exportKwh;\n uit.ongeprijsdeImportKwh = Math.max(0, gemetenImport - uit.importKwh);\n uit.ongeprijsdeExportKwh = Math.max(0, gemetenExport - uit.exportKwh);\n uit.importKwh = gemetenImport;\n uit.exportKwh = gemetenExport;\n uit.gemetenMs = gemetenMs;\n uit.prijsOnvolledig = uit.ongeprijsdeImportKwh + uit.ongeprijsdeExportKwh > 0.000001;\n return uit;\n }\n\n function actueelProjectieToon() {\n if (!isActueelPagina()) {\n return;\n }\n\n var bestaand =\n actueelZorgBedieningsRij();\n\n if (!bestaand) {\n return;\n }\n\n var info =\n document.getElementById(\n 'be-actueel-resultaat'\n );\n\n if (!info) {\n return;\n }\n\n /*\n * Het dagresultaat blijft altijd zichtbaar.\n * Toggle AAN = all-in resultaat.\n * Toggle UIT = kaal resultaat.\n */\n info.hidden = false;\n\n\n var nu =\n Date.now();\n\n var punten =\n actueelDagPunten\n .filter(\n function (p) {\n return (\n p &&\n Number.isFinite(p.x) &&\n Number.isFinite(p.y)\n );\n }\n );\n if (punten.length < 2) {\n info.textContent =\n actueelAan\n ? 'Voorlopig all-in resultaat — wachten op meetgegevens.'\n : 'Voorlopig kaal resultaat — wachten op meetgegevens.';\n\n return;\n }\n var beginVandaag =\n new Date();\n\n beginVandaag.setHours(\n 0,\n 0,\n 0,\n 0\n );\n\n var vanaf =\n beginVandaag.getTime();\n\n var tot =\n Math.min(\n nu,\n punten[\n punten.length - 1\n ].x\n );\n\n var res = actueelProjectieBereken(\n punten,\n actueelRijen,\n vanaf,\n tot,\n nu\n );\n bewaarVoorlopigeDag(\n iso(beginVandaag),\n res,\n vanaf,\n tot\n );\n\n if (res.ongeldig || !(res.gemetenMs > 0)) {\n info.textContent = 'Resultaat vandaag — nog geen passende metingen en prijzen';\n return;\n }\n\n var tijd = function (t) { return new Date(t).toLocaleString('nl-NL', {\n day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit'\n }); };\n\n var geld = function (n) {\n return (\n (n < 0 ? '− ' : '+ ') +\n '€ ' +\n Math.abs(n).toLocaleString(\n 'nl-NL',\n {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2\n }\n )\n );\n };\n\n var tekst =\n '' +\n 'Resultaat vandaag' +\n '' +\n\n '' +\n (\n res.stukken\n ? geld(res.bedrag)\n : '— · prijzen ontbreken'\n ) +\n '' +\n\n '' +\n res.importKwh\n .toFixed(1)\n .replace('.', ',') +\n ' kWh import' +\n ' · ' +\n res.exportKwh\n .toFixed(1)\n .replace('.', ',') +\n ' kWh export' +\n (\n actueelAan\n ? ''\n : ' · kaal'\n ) +\n\n '';\n\n\n\n info.title = res.prijsOnvolledig\n ? 'Voorlopig bedrag. Tarief ontbreekt voor ' +\n (res.ongeprijsdeImportKwh + res.ongeprijsdeExportKwh).toLocaleString('nl-NL', {maximumFractionDigits:2}) + ' kWh.'\n : res.historischKwh > 0.000001 ? 'Voorlopig bedrag met herstelde historische tarieven.' : '';\n\n if (\n info.__beResultaatTekst !== tekst\n ) {\n info.innerHTML = tekst;\n info.__beResultaatTekst = tekst;\n if(beDarkOn){beDarkPending.add(info);beDarkRefresh();}\n }\n }\n\n function actueelOnderhoud() {\n verversKlantUitPad();\n\n var nieuweKlant =\n !!KLANT &&\n KLANT !==\n actueelLaatsteKlant;\n\n var nuActueel =\n isActueelPagina();\n\n if (!nuActueel) {\n actueelWasPagina =\n false;\n\n if (actueelVoetnootTimer) {\n clearInterval(\n actueelVoetnootTimer\n );\n\n actueelVoetnootTimer =\n null;\n }\n\n return;\n }\n\n actueelInstalleerBridge();\n\n actueelBouwSchakelaar();\n\n tariefZorgKnop();\n\n actueelProjectieToon();\n\n actueelZetVoetnoot();\n\n actueelVolgVoetnoot();\n\n actueelVolgSchema();\n\n actueelWerkSchemaBij();\n\n if (\n !actueelWasPagina ||\n nieuweKlant\n ) {\n actueelWasPagina =\n true;\n\n actueelLaatsteKlant =\n KLANT;\n\n actueelStuur();\n\n if (!actueelRijen.length) {\n actueelHaal();\n }\n\n actueelHaalDagPunten();\n\n tariefStuur();\n }\n }\n\n function startActueelAllin() {\n if (actueelGestart) {\n return;\n }\n\n actueelGestart =\n true;\n\n actueelVoegMobieleStijlToe();\n\n actueelOnderhoud();\n\n actueelOnderhoudTimer =\n setInterval(\n function () {\n actueelOnderhoud();\n },\n 3000\n );\n\n setInterval(function () {\n if (isActueelPagina()) actueelHaal();\n }, 15000);\n document.addEventListener('visibilitychange', function () {\n if (!document.hidden && isActueelPagina()) actueelHaal();\n });\n actueelHaalTimer =\n setInterval(\n function () {\n if (\n isActueelPagina()\n ) {\n actueelHaalDagPunten();\n }\n },\n 30000\n );\n\n if (isActueelPagina()) {\n actueelInstalleerBridge();\n\n actueelStuur();\n\n var pogingen = 0;\n\n var wacht =\n setInterval(\n function () {\n pogingen++;\n\n actueelOnderhoud();\n\n if (\n actueelLegenda() &&\n document.getElementById(\n 'battery-chart'\n )\n ) {\n clearInterval(\n wacht\n );\n\n actueelHaal();\n actueelHaalDagPunten();\n\n } else if (\n pogingen > 120 ||\n !isActueelPagina()\n ) {\n clearInterval(\n wacht\n );\n }\n },\n 500\n );\n }\n }\n\n\n var voorlopigResultatenBezig = false;\n var voorlopigResultatenLaatst = 0;\n\n function resultatenHaalVoorlopigeDagen() {\n if (\n location.pathname.indexOf(\n '/results'\n ) === -1 ||\n !KLANT\n ) {\n return Promise.resolve(\n false\n );\n }\n\n var nu =\n Date.now();\n\n if (\n voorlopigResultatenBezig ||\n nu -\n voorlopigResultatenLaatst <\n 30000\n ) {\n return Promise.resolve(\n false\n );\n }\n\n voorlopigResultatenBezig =\n true;\n\n voorlopigResultatenLaatst =\n nu;\n\n /*\n * Vandaag plus de laatste vier kalenderdagen controleren.\n *\n * Belangrijk:\n * - Echte maanddata wint altijd.\n * - Ook opgeslagen voorlopige dagen worden opnieuw gecontroleerd.\n * - Vandaag mag wel blijven verversen zolang hij nog provisional is.\n * - Ontbrekende dagen worden uitsluitend uit gedateerde historie en prijzen gereconstrueerd.\n */\n var beginBereik =\n new Date();\n\n beginBereik.setHours(\n 0,\n 0,\n 0,\n 0\n );\n\n beginBereik.setDate(\n beginBereik.getDate() - 4\n );\n\n var dagKeys = [];\n var maanden = {};\n\n var cursor =\n new Date(\n beginBereik.getTime()\n );\n\n var vandaag =\n new Date();\n\n vandaag.setHours(\n 0,\n 0,\n 0,\n 0\n );\n\n var vandaagKey =\n iso(vandaag);\n\n while (\n cursor.getTime() <=\n vandaag.getTime()\n ) {\n var key =\n iso(cursor);\n\n dagKeys.push(\n key\n );\n\n maanden[\n key.slice(0, 7)\n ] = true;\n\n cursor.setDate(\n cursor.getDate() + 1\n );\n }\n\n var maandControles =\n Promise.all(\n Object.keys(maanden)\n .map(\n function (mk) {\n var m =\n new URL(\n location.origin +\n '/customer/' +\n KLANT +\n '/results-v2-api/'\n );\n\n m.searchParams.set(\n 'interval',\n 'month'\n );\n\n m.searchParams.set(\n 'period',\n mk\n );\n\n m.searchParams.set(\n 'include_vat',\n '1'\n );\n\n m.searchParams.set(\n 'include_delivery_cost',\n '1'\n );\n\n return beHaalBron(\n m,\n {\n credentials:\n 'same-origin',\n\n cache:\n 'no-store'\n }\n )\n .then(\n function (r) {\n return r.ok\n ? r.json()\n : null;\n }\n )\n .then(\n function (j) {\n return {\n key:\n mk,\n\n data:\n j\n };\n }\n )\n .catch(\n function () {\n return {\n key:\n mk,\n\n data:\n null\n };\n }\n );\n }\n )\n );\n\n return maandControles\n .then(\n function (maandSets) {\n var opslag =\n leesVoorlopigeDagen();\n\n var echt = {};\n var gewijzigd = false;\n\n (maandSets || [])\n .forEach(\n function (set) {\n var j =\n set &&\n set.data;\n\n if (\n !j || j.interval !== 'month' || j.period !== set.key ||\n !Array.isArray(\n j.series\n )\n ) {\n return;\n }\n\n j.series.forEach(\n function (r) {\n var k =\n r &&\n (\n r.date ||\n r.day\n );\n\n if (\n k && String(k).slice(0, 7) === set.key &&\n ['import_kwh', 'export_kwh', 'total_buy_eur', 'total_sell_eur'].every(function (f) { return Number.isFinite(r[f]); }) &&\n /^\\d{4}-\\d{2}-\\d{2}$/.test(\n String(k)\n )\n ) {\n echt[\n String(k)\n ] = r;\n }\n }\n );\n }\n );\n\n /*\n * Zodra de echte datum in results-v2-api staat,\n * de lokale provisional meteen opruimen.\n */\n Object.keys(opslag)\n .forEach(\n function (k) {\n if (echt[k]) {\n bewaarVoorlopigeAudit(\n k,\n opslag[k],\n echt[k]\n );\n\n delete opslag[k];\n gewijzigd = true;\n }\n }\n );\n\n if (gewijzigd) {\n schrijfVoorlopigeDagen(\n opslag\n );\n }\n\n var kandidaten =\n dagKeys.filter(\n function (k) {\n if (echt[k]) {\n return false;\n }\n\n return true;\n }\n );\n\n if (!kandidaten.length) {\n return gewijzigd;\n }\n\n // Bestaande, bewezen snapshots blijven tijdens opnieuw ophalen behouden.\n schrijfVoorlopigeDagen(opslag);\n // Geef verwijderde resultaten direct door, ook wanneer herladen mislukt.\n gewijzigd = true;\n if (laatste) plan();\n\n var dagApi = Promise.resolve(kandidaten.map(function (key) {\n return { key: key, data: null };\n }));\n\n return dagApi\n .then(\n function (dagResultaten) {\n var fallbackKeys = [];\n var veranderd =\n gewijzigd;\n\n (dagResultaten || [])\n .forEach(\n function (item) {\n var j =\n item &&\n item.data;\n\n var dagKey =\n item &&\n item.key;\n\n var bruikbaar =\n false;\n\n // Dagtotalen alleen zijn geen reproduceerbare kwartierbron.\n // Gebruik uitsluitend historie + prijzen met exacte tijdstippen.\n if (\n dagKey &&\n !bruikbaar\n ) {\n fallbackKeys.push(\n dagKey\n );\n }\n }\n );\n\n if (!fallbackKeys.length) {\n return veranderd;\n }\n\n var eerste =\n new Date(\n fallbackKeys[0] +\n 'T00:00:00'\n );\n\n fallbackKeys.forEach(\n function (k) {\n var d =\n new Date(\n k +\n 'T00:00:00'\n );\n\n if (\n d.getTime() <\n eerste.getTime()\n ) {\n eerste = d;\n }\n }\n );\n\n var u =\n new URL(\n location.origin +\n '/customer/current-history/api/'\n );\n\n u.searchParams.set(\n 'installation',\n KLANT\n );\n\n u.searchParams.set(\n 'start',\n String(\n eerste.getTime()\n )\n );\n\n u.searchParams.set(\n 'end',\n String(nu)\n );\n\n var prijzen =\n beHaalBron(\n location.origin +\n '/customer/current-prices/api/',\n {\n credentials:\n 'same-origin',\n\n cache:\n 'no-store'\n }\n ).then(\n function (r) {\n return r.ok\n ? r.json()\n : null;\n }\n );\n\n // Vraag iedere dag apart op: een groot bereik kan door de API\n // worden begrensd, waardoor de recente dagen ontbreken.\n var historie = Promise.all(fallbackKeys.map(function (key) {\n var grenzen = voorlopigeDagGrenzen(key);\n var dagUrl = new URL('/customer/current-history/api/', location.origin);\n dagUrl.searchParams.set('installation', KLANT);\n dagUrl.searchParams.set('start', String(grenzen.start));\n dagUrl.searchParams.set('end', String(Math.min(nu, grenzen.eind)));\n return beHaalBron(dagUrl.href, { credentials: 'same-origin', cache: 'no-store' })\n .then(function (r) { return r.ok ? r.json() : null; })\n .then(function (j) {\n return j && Array.isArray(j.rows) ? j.rows.filter(function (r) {\n return r && Number.isFinite(r.t) && r.t >= grenzen.start &&\n r.t <= Math.min(nu, grenzen.eind) && Number.isFinite(r.po);\n }) : [];\n }).catch(function () { return []; });\n })).then(function (dagen) {\n var perTijd = new Map();\n dagen.forEach(function (rijen) { rijen.forEach(function (r) { perTijd.set(r.t, r); }); });\n return { rows: Array.from(perTijd.values()) };\n });\n\n return Promise.all([\n prijzen,\n historie\n ]).then(\n function (res) {\n var jp =\n res[0];\n\n var jh =\n res[1];\n\n if (\n !jp ||\n !Array.isArray(\n jp.prices\n ) ||\n !jh ||\n !Array.isArray(\n jh.rows\n )\n ) {\n return veranderd;\n }\n\n var evts = {};\n\n (\n jp.afrrEventIsps || []\n ).forEach(\n function (t) {\n evts[t] = true;\n }\n );\n\n var prijsRijen = jp.prices.map(actueelPrijsRij)\n .filter(function(r){return Number.isFinite(r.ki)||Number.isFinite(r.ke);});\n\n var punten =\n jh.rows\n .filter(\n function (r) {\n return (\n r &&\n Number.isFinite(r.t) &&\n Number.isFinite(r.po)\n );\n }\n )\n .map(\n function (r) {\n return {\n x:\n r.t,\n\n y:\n r.po\n };\n }\n )\n .sort(\n function (a, b) {\n return a.x - b.x;\n }\n );\n\n if (\n punten.length < 2 ||\n !prijsRijen.length\n ) {\n return veranderd;\n }\n\n fallbackKeys.forEach(\n function (dagKey) {\n var dag =\n new Date(\n dagKey +\n 'T00:00:00'\n );\n\n var volgende =\n new Date(\n dag.getTime()\n );\n\n volgende.setDate(\n volgende.getDate() + 1\n );\n\n var vanaf =\n dag.getTime();\n\n var tot =\n Math.min(\n nu,\n volgende.getTime()\n );\n\n var berekend =\n actueelProjectieBereken(\n punten,\n prijsRijen,\n vanaf,\n tot,\n nu\n );\n\n if (\n berekend.stukken > 0\n ) {\n bewaarVoorlopigeDag(\n dagKey,\n berekend,\n vanaf,\n tot\n );\n\n veranderd =\n true;\n }\n }\n );\n\n return veranderd;\n }\n );\n }\n );\n }\n )\n .catch(\n function (e) {\n console.warn(\n '[BE Resultaten voorlopig] ophalen mislukt:',\n e.message\n );\n\n return false;\n }\n )\n .then(\n function (ok) {\n voorlopigResultatenBezig =\n false;\n\n return ok;\n }\n );\n }\n\n\n /*\n * ============================================================\n * RESULTATEN — datalaag\n * ============================================================\n */\n\n function naarIncl(\n rij,\n pv\n ) {\n var imp =\n getal(\n rij.import_kwh\n );\n\n var exp =\n getal(\n rij.export_kwh\n );\n\n var buy =\n getal(\n rij.total_buy_eur\n );\n\n var sell =\n getal(\n rij.total_sell_eur\n );\n\n if (!pv.include_vat) {\n buy *= BTW;\n sell *= BTW;\n }\n\n if (\n !pv.include_delivery_cost\n ) {\n buy +=\n imp * OPSLAG;\n\n sell -=\n exp * OPSLAG;\n }\n\n return {\n bronAanwezig: ['import_kwh', 'export_kwh', 'total_buy_eur', 'total_sell_eur'].every(function (k) {\n return Number.isFinite(rij[k]);\n }),\n key:\n rij.month ||\n rij.date ||\n rij.day ||\n null,\n\n status:\n rij.status ||\n null,\n\n imp:\n imp,\n\n exp:\n exp,\n\n inkoop:\n buy,\n\n verkoop:\n sell,\n\n afrrImp:\n getal(\n rij.afrr_import_kwh\n ),\n\n afrrExp:\n getal(\n rij.afrr_export_kwh\n ),\n\n laden:\n getal(\n rij.battery_charged_kwh\n ),\n\n ontladen:\n getal(\n rij.battery_discharged_kwh\n ),\n\n pv:\n getal(\n rij.pv_estimated_kwh\n ),\n\n afrrVergoeding:\n getal(\n rij.balance_energie_eur\n ) +\n getal(\n rij.deal_customer_share_eur\n ),\n\n dealsAantal:\n getal(\n rij.deals_count\n ),\n\n ruwInkoop:\n getal(\n rij.total_buy_eur\n ),\n\n ruwVerkoop:\n getal(\n rij.total_sell_eur\n ),\n\n ruwPrijsInk:\n rij.avg_buy_price_eur_per_kwh,\n\n ruwPrijsVerk:\n rij.avg_sell_price_eur_per_kwh\n };\n }\n\n var cacheJaar = {};\n\n function haalJaar(jaar) {\n if (cacheJaar[jaar]) {\n return Promise.resolve(\n cacheJaar[jaar]\n );\n }\n\n var u =\n new URL(\n location.origin +\n '/customer/' +\n KLANT +\n '/results-v2-api/'\n );\n\n u.searchParams.set(\n 'interval',\n 'year'\n );\n\n u.searchParams.set(\n 'period',\n String(jaar)\n );\n\n u.searchParams.set(\n 'include_vat',\n '1'\n );\n\n u.searchParams.set(\n 'include_delivery_cost',\n '1'\n );\n\n return origFetch(\n u,\n {\n credentials:\n 'same-origin'\n }\n )\n\n .then(\n function (r) {\n return r.ok\n ? r.json()\n : null;\n }\n )\n\n .then(\n function (j) {\n var rijen =\n (\n j &&\n Array.isArray(\n j.series\n )\n )\n ? j.series.map(\n function (s) {\n return naarIncl(\n s,\n j.pricing || {}\n );\n }\n )\n : [];\n\n cacheJaar[jaar] =\n rijen;\n\n return rijen;\n }\n )\n\n .catch(\n function () {\n cacheJaar[jaar] = [];\n return [];\n }\n );\n }\n\n var contractCache = null;\n\n function haalContractjaar() {\n if (contractCache) {\n return Promise.resolve(\n contractCache\n );\n }\n\n var jaren = {};\n\n contractMaanden()\n .forEach(\n function (mk) {\n jaren[\n mk.slice(0, 4)\n ] = true;\n }\n );\n\n return Promise.all(\n Object.keys(jaren)\n .map(haalJaar)\n ).then(\n function (sets) {\n var op = {};\n\n sets.forEach(\n function (rijen) {\n rijen.forEach(\n function (r) {\n if (r.key) {\n op[r.key] =\n r;\n }\n }\n );\n }\n );\n\n contractCache =\n contractMaanden()\n .map(\n function (mk) {\n return (\n op[mk] ||\n {\n key: mk,\n leeg: true,\n imp: 0,\n exp: 0,\n inkoop: 0,\n verkoop: 0,\n laden: 0,\n ontladen: 0,\n pv: 0,\n afrrImp: 0,\n afrrExp: 0,\n afrrVergoeding: 0,\n dealsAantal: 0\n }\n );\n }\n );\n\n return contractCache;\n }\n );\n }\n\n function grenzen() {\n try {\n var e =\n document.getElementById(\n 'results-bounds-data'\n );\n\n return e\n ? JSON.parse(\n e.textContent\n )\n : null;\n\n } catch (e) {\n return null;\n }\n }\n\n var IMP_FACTOR = {\n Jan: 1.00,\n Feb: 0.62,\n Mrt: 0.25,\n Apr: 0.09,\n Mei: 0.02,\n Jun: 0.01,\n Jul: 0.02,\n Aug: 0.03,\n Sep: 0.10,\n Okt: 0.32,\n Nov: 0.57,\n Dec: 0.87\n };\n\n var EXP_FACTOR = {\n Jan: 0.10,\n Feb: 0.18,\n Mrt: 0.45,\n Apr: 1.00,\n Mei: 1.30,\n Jun: 1.32,\n Jul: 1.18,\n Aug: 0.96,\n Sep: 0.74,\n Okt: 0.28,\n Nov: 0.12,\n Dec: 0.08\n };\n\n var PROFIEL = {\n Jan: {\n w: 1.00,\n z: 0.00\n },\n\n Feb: {\n w: 0.72,\n z: 0.00\n },\n\n Mrt: {\n w: 0.28,\n z: 0.10\n },\n\n Apr: {\n w: 0.08,\n z: 0.28\n },\n\n Mei: {\n w: 0.00,\n z: 0.62\n },\n\n Jun: {\n w: 0.00,\n z: 1.00\n },\n\n Jul: {\n w: 0.00,\n z: 0.86\n },\n\n Aug: {\n w: 0.04,\n z: 0.52\n },\n\n Sep: {\n w: 0.14,\n z: 0.24\n },\n\n Okt: {\n w: 0.26,\n z: 0.08\n },\n\n Nov: {\n w: 0.64,\n z: 0.00\n },\n\n Dec: {\n w: 0.90,\n z: 0.00\n }\n };\n\n function mk3(mk) {\n return MND_KORT[\n parseInt(\n String(mk)\n .split('-')[1],\n 10\n ) - 1\n ];\n }\n\n function schatKwh(\n mk,\n bekend,\n factoren,\n terugval\n ) {\n var doelF =\n factoren[\n mk3(mk)\n ] || 0.5;\n\n if (!bekend.length) {\n return (\n terugval *\n doelF\n );\n }\n\n var gew = 0;\n var som = 0;\n\n bekend.forEach(\n function (b) {\n var ankerF =\n factoren[\n mk3(b.key)\n ] || 0.1;\n\n if (\n ankerF < 0.05\n ) {\n return;\n }\n\n var vergelijkbaar =\n Math.min(\n ankerF,\n doelF\n ) /\n Math.max(\n ankerF,\n doelF\n );\n\n var g =\n 0.3 +\n vergelijkbaar *\n 0.7;\n\n gew += g;\n\n som +=\n b.waarde *\n (\n doelF /\n ankerF\n ) *\n g;\n }\n );\n\n return gew > 0\n ? som / gew\n : 0;\n }\n\n function ankers(\n bedragPerMaand\n ) {\n var bekend =\n Object.keys(\n bedragPerMaand\n ).map(\n function (k) {\n return {\n m:\n mk3(k),\n\n v:\n bedragPerMaand[k]\n };\n }\n );\n\n function kies(namen) {\n return bekend.filter(\n function (b) {\n return (\n namen.indexOf(\n b.m\n ) !== -1\n );\n }\n );\n }\n\n var winterSet =\n kies([\n 'Nov',\n 'Dec',\n 'Jan',\n 'Feb'\n ]);\n\n var zomerSet =\n kies([\n 'Mei',\n 'Jun',\n 'Jul',\n 'Aug'\n ]);\n\n var tussenSet =\n kies([\n 'Mrt',\n 'Apr',\n 'Sep',\n 'Okt'\n ]);\n\n var winter =\n winterSet.length\n ? Math.min.apply(\n null,\n winterSet.map(\n function (b) {\n return b.v;\n }\n )\n )\n : -260;\n\n var zomer =\n zomerSet.length\n ? Math.max.apply(\n null,\n zomerSet.map(\n function (b) {\n return b.v;\n }\n )\n )\n : 110;\n\n var tussen =\n tussenSet.length\n ? (\n tussenSet.reduce(\n function (\n a,\n b\n ) {\n return (\n a +\n b.v\n );\n },\n 0\n ) /\n tussenSet.length\n )\n : 25;\n\n if (\n tussen < winter\n ) {\n tussen =\n winter + 40;\n }\n\n if (\n zomer < tussen\n ) {\n zomer =\n tussen + 40;\n }\n\n return {\n winter:\n winter,\n\n tussen:\n tussen,\n\n zomer:\n zomer\n };\n }\n\n function prognoseBedrag(\n mk,\n a\n ) {\n var p =\n PROFIEL[\n mk3(mk)\n ] || {\n w: 0.25,\n z: 0.20\n };\n\n return (\n a.tussen -\n (\n a.tussen -\n a.winter\n ) *\n p.w +\n (\n a.zomer -\n a.tussen\n ) *\n p.z\n );\n }\n\n function salderingReeks(\n rijen,\n metPrognose\n ) {\n var bekendImp = [];\n var bekendExp = [];\n\n rijen.forEach(\n function (r) {\n if (r.leeg) {\n return;\n }\n\n if (r.imp > 0) {\n bekendImp.push({\n key:\n r.key,\n\n waarde:\n r.imp\n });\n }\n\n if (r.exp > 0) {\n bekendExp.push({\n key:\n r.key,\n\n waarde:\n r.exp\n });\n }\n }\n );\n\n var cumImp = 0;\n var cumExp = 0;\n\n var reeks =\n rijen.map(\n function (r) {\n var geschat =\n !!r.leeg;\n\n var imp =\n r.imp;\n\n var exp =\n r.exp;\n\n if (geschat) {\n if (metPrognose) {\n imp =\n schatKwh(\n r.key,\n bekendImp,\n IMP_FACTOR,\n 150\n );\n\n exp =\n schatKwh(\n r.key,\n bekendExp,\n EXP_FACTOR,\n 0\n );\n\n } else {\n imp = 0;\n exp = 0;\n }\n }\n\n cumImp += imp;\n cumExp += exp;\n\n return {\n key:\n r.key,\n\n imp:\n imp,\n\n exp:\n exp,\n\n cumImp:\n cumImp,\n\n cumExp:\n cumExp,\n\n ongesaldeerd:\n 0,\n\n gesaldeerd:\n exp,\n\n geschat:\n geschat,\n\n overgeslagen:\n geschat &&\n !metPrognose,\n\n naSaldering:\n naSaldering(\n r.key\n )\n };\n }\n );\n\n var saldImp = 0;\n var saldExp = 0;\n\n reeks.forEach(\n function (s) {\n if (\n !s.naSaldering\n ) {\n saldImp +=\n s.imp;\n\n saldExp +=\n s.exp;\n }\n }\n );\n\n var over =\n Math.max(\n 0,\n saldExp -\n saldImp\n );\n\n for (\n var i =\n reeks.length - 1;\n\n i >= 0;\n\n i--\n ) {\n var s =\n reeks[i];\n\n if (s.naSaldering) {\n s.ongesaldeerd =\n s.exp;\n\n } else {\n s.ongesaldeerd =\n Math.min(\n s.exp,\n over\n );\n\n over -=\n s.ongesaldeerd;\n }\n\n s.gesaldeerd =\n s.exp -\n s.ongesaldeerd;\n }\n\n return reeks;\n }\n\n function bereken(\n r,\n expOngesaldeerd,\n dagen\n ) {\n var imp = r.imp;\n var exp = r.exp;\n\n var onges =\n Math.min(\n getal(\n expOngesaldeerd\n ),\n exp\n );\n\n var ges =\n Math.max(\n 0,\n exp -\n onges\n );\n\n var balansInk =\n -r.inkoop;\n\n var balansVerk =\n r.verkoop;\n\n var balansNetto =\n balansInk +\n balansVerk;\n\n // Match the priced portion used by Actueel, while retaining all measured kWh.\n var ebImp = r.voorlopig && Number.isFinite(r.geprijsdeImportKwh) ? r.geprijsdeImportKwh : imp;\n var ebExp = r.voorlopig && Number.isFinite(r.geprijsdeExportKwh) ? r.geprijsdeExportKwh : exp;\n var ebInk = -ebImp * EB;\n var ebVerk = Math.max(0, ebExp - onges) * EB;\n\n var eb =\n ebInk +\n ebVerk;\n\n var stroom =\n balansNetto +\n eb;\n\n var vast =\n getal(dagen) *\n VAST_DAG;\n\n var allinInk =\n balansInk +\n ebInk;\n\n var allinVerk =\n balansVerk +\n ebVerk;\n\n return {\n key:\n r.key,\n\n imp:\n imp,\n\n exp:\n exp,\n\n balansInk:\n balansInk,\n\n balansVerk:\n balansVerk,\n\n balansNetto:\n balansNetto,\n\n eb:\n eb,\n\n ebInk:\n ebInk,\n\n ebVerk:\n ebVerk,\n\n ongesaldeerd:\n onges,\n\n gesaldeerd:\n ges,\n\n allinInk:\n allinInk,\n\n allinVerk:\n allinVerk,\n\n stroom:\n stroom,\n\n vast:\n vast,\n\n voorschot:\n voorschotVoor(\n r.key,\n getal(dagen),\n false\n ),\n\n totaal:\n stroom +\n vast,\n\n saldo:\n stroom +\n vast +\n voorschotVoor(\n r.key,\n getal(dagen),\n false\n ),\n\n dagen:\n getal(dagen),\n\n prijsInk:\n imp > 0\n ? Math.abs(\n allinInk\n ) / imp\n : 0,\n\n prijsVerk:\n exp > 0\n ? allinVerk / exp\n : 0,\n\n prijsInkBalans:\n imp > 0\n ? r.inkoop / imp\n : 0,\n\n prijsVerkBalans:\n exp > 0\n ? r.verkoop / exp\n : 0,\n\n laden:\n r.laden,\n\n ontladen:\n r.ontladen,\n\n pv:\n r.pv,\n\n afrrImp:\n r.afrrImp,\n\n afrrExp:\n r.afrrExp,\n\n afrrVergoeding:\n r.afrrVergoeding,\n\n dealsAantal:\n r.dealsAantal,\n\n geschat:\n !!r.leeg\n };\n }\n\n function fpr(rijen) {\n if (\n !rijen ||\n !rijen.length\n ) {\n return null;\n }\n\n var laad = 0;\n var ont = 0;\n var laadKwh = 0;\n var ontKwh = 0;\n var meegeteld = 0;\n\n rijen.forEach(\n function (a) {\n if (\n !a.laden &&\n !a.ontladen\n ) {\n return;\n }\n\n if (\n !a.prijsInk &&\n !a.prijsVerk\n ) {\n return;\n }\n\n var viaNet =\n Math.min(\n a.exp,\n a.ontladen\n );\n\n var viaHuis =\n Math.max(\n 0,\n a.ontladen -\n viaNet\n );\n\n laad +=\n a.laden *\n a.prijsInk;\n\n ont +=\n viaNet *\n a.prijsVerk +\n viaHuis *\n a.prijsInk;\n\n laadKwh +=\n a.laden;\n\n ontKwh +=\n a.ontladen;\n\n meegeteld++;\n }\n );\n\n if (laad <= 0) {\n return null;\n }\n\n return {\n ratio:\n ont / laad,\n\n laadKwh:\n laadKwh,\n\n ontlaadKwh:\n ontKwh,\n\n gemLaad:\n laadKwh > 0\n ? laad / laadKwh\n : 0,\n\n gemOntlaad:\n ontKwh > 0\n ? ont / ontKwh\n : 0,\n\n netto:\n ont - laad,\n\n retour:\n laadKwh > 0\n ? ontKwh / laadKwh\n : 0,\n\n perioden:\n meegeteld\n };\n }\n\n function regel(\n label,\n waarde,\n o\n ) {\n o = o || {};\n\n var kl =\n o.neutraal\n ? D.inkt\n : kleur(\n waarde\n );\n\n var fs =\n o.groot\n ? '15.5px'\n : '12.5px';\n\n var streep =\n o.streep\n ? (\n 'border-top:1px solid ' +\n D.rand +\n ';margin-top:8px;' +\n 'padding-top:9px;'\n )\n : '';\n\n var sub =\n o.sub\n ? (\n '
' +\n o.sub +\n '
'\n )\n : '';\n\n return (\n '
' +\n\n '
' +\n label +\n sub +\n '
' +\n\n '
' +\n eurT(waarde) +\n '
' +\n\n '
'\n );\n }\n\n function vakje(\n kop,\n groot,\n klein,\n kl\n ) {\n return (\n '
' +\n\n '
' +\n kop +\n '
' +\n\n '
' +\n groot +\n '
' +\n\n '
' +\n klein +\n '
' +\n\n '
'\n );\n }\n\n function bouwHoofdkaart(\n a,\n ctx\n ) {\n var c =\n kaart(\n 'be-hoofd',\n null\n );\n\n var perMaand =\n ctx.interval ===\n 'month';\n\n var contractPeriode =\n ctx.interval ===\n 'rolling_year' &&\n Number.isFinite(\n ctx.contractSaldo\n );\n\n var volDagen =\n perMaand &&\n a.key\n ? dagenInMaand(\n a.key\n )\n : 0;\n\n var deelPeriode =\n perMaand &&\n volDagen > 0 &&\n a.dagen > 0 &&\n a.dagen < volDagen;\n\n var stroomKosten =\n a.allinInk +\n a.allinVerk +\n (perMaand ? (ctx.voorlopigStroom || 0) : 0);\n\n function rij(\n label,\n detail,\n bedrag,\n o\n ) {\n o = o || {};\n\n var streep =\n o.streep\n ? (\n 'border-top:1px solid ' +\n D.rand +\n ';'\n )\n : '';\n\n var gew =\n o.dik\n ? '600'\n : '400';\n\n return (\n '' +\n\n '' +\n label +\n '' +\n\n '' +\n (detail || '') +\n '' +\n\n '' +\n eurT(bedrag) +\n '' +\n\n ''\n );\n }\n\n var tabel =\n '' +\n\n '' +\n '' +\n '' +\n '' +\n '' +\n\n rij(\n 'Afname van het net',\n kwh(a.imp) +\n ' \\u00e0 ' +\n eur(\n a.prijsInk,\n 3\n ),\n a.allinInk\n ) +\n\n rij(\n 'Teruglevering',\n kwh(a.exp) +\n ' \\u00e0 ' +\n eur(\n a.prijsVerk,\n 3\n ),\n a.allinVerk\n ) +\n\n (\n perMaand && ctx.voorlopigAantal > 0\n ? rij(\n 'Voorlopige kosten / opbrengsten',\n ctx.voorlopigAantal + ' dag(en) · nog niet definitief',\n ctx.voorlopigStroom\n )\n : ''\n ) +\n\n (\n perMaand\n ? ''\n : rij(\n 'Netbeheer',\n num(\n a.dagen,\n 0\n ) +\n ' dagen',\n a.vast,\n {\n streep: true\n }\n )\n ) +\n\n (\n contractPeriode &&\n Math.abs(\n ctx.contractWerkelijk -\n a.totaal\n ) > 0.005\n ? rij(\n 'Voorlopige aanvulling',\n 'nog niet definitief bij Balans',\n ctx.contractWerkelijk -\n a.totaal\n )\n : ''\n ) +\n\n (\n contractPeriode\n ? rij(\n 'Betaald voorschot',\n ctx.contractMaanden +\n ' × ' +\n eur(\n cfg.voorschot\n ),\n ctx.contractBetaald,\n {\n neutraal:\n true\n }\n )\n : ''\n ) +\n\n rij(\n perMaand\n ? 'Stroomkosten'\n : (\n contractPeriode\n ? 'Huidig contractsaldo'\n : 'Totaal'\n ),\n '',\n perMaand\n ? stroomKosten\n : (\n contractPeriode\n ? ctx.contractSaldo\n : a.totaal\n ),\n {\n dik: true,\n streep: true\n }\n ) +\n\n '
';\n\n var eindbedrag =\n perMaand\n ? stroomKosten\n : (\n contractPeriode\n ? ctx.contractSaldo\n : a.totaal\n );\n\n var kop =\n '
' +\n\n '
' +\n\n '
' +\n ctx.naam +\n '
' +\n\n '
' +\n\n (\n perMaand\n ? (\n num(\n a.dagen,\n 0\n ) +\n (\n deelPeriode\n ? ' van ' +\n volDagen\n : ''\n ) +\n ' dagen \\u00b7 alleen afname en teruglevering'\n )\n : (\n (\n a.dagen\n ? (\n num(\n a.dagen,\n 0\n ) +\n ' dagen \\u00b7 '\n )\n : ''\n ) +\n (\n contractPeriode\n ? 'contractstand incl. voorschotten'\n : 'alles inbegrepen'\n )\n )\n ) +\n\n '
' +\n\n '
' +\n\n '
' +\n\n '
' +\n eurT(\n eindbedrag\n ) +\n '
' +\n\n '
' +\n\n (\n perMaand\n ? 'stroomkosten'\n : contractPeriode\n ? (\n eindbedrag < 0\n ? 'nog te betalen'\n : 'te ontvangen'\n )\n : (\n eindbedrag < 0\n ? 'kosten'\n : 'opbrengst'\n )\n ) +\n\n '
' +\n\n '
' +\n\n '
';\n\n c.innerHTML =\n kop +\n tabel;\n\n if (deelPeriode) {\n c.appendChild(\n el(\n 'div',\n 'margin-top:10px;' +\n 'font-size:11px;' +\n 'color:' +\n D.grijs +\n ';line-height:1.5;',\n 'Balans heeft ' +\n a.dagen +\n ' van de ' +\n volDagen +\n ' dagen verwerkt. ' +\n 'Dit stroomkostenblok bevat alleen afname en teruglevering. ' +\n 'Netbeheer, voorschotten en de vaste belastingvermindering ' +\n 'zijn hier niet meegerekend.'\n )\n );\n }\n\n return c;\n }\n\n function bouwPrijsopbouw(a) {\n var c =\n sectie(\n 'be-prijs',\n 'Prijsopbouw per kWh'\n );\n\n function kolomPrijs(\n titel,\n balansPrijs,\n ebDeel,\n allinPrijs,\n kl,\n voet\n ) {\n return (\n '
' +\n\n '
' +\n titel +\n '
' +\n\n '
' +\n\n '' +\n 'Volgens Balans' +\n '' +\n\n '' +\n eur(\n balansPrijs,\n 3\n ) +\n '' +\n\n '
' +\n\n '
' +\n\n '' +\n 'Energiebelasting' +\n '' +\n\n '' +\n\n (\n ebDeel < 0\n ? '\\u2212'\n : '+'\n ) +\n ' ' +\n eur(\n Math.abs(\n ebDeel\n ),\n 3\n ) +\n\n '' +\n\n '
' +\n\n '
' +\n\n '' +\n 'All-in' +\n '' +\n\n '' +\n eur(\n allinPrijs,\n 3\n ) +\n '' +\n\n '
' +\n\n '
' +\n voet +\n '
' +\n\n '
'\n );\n }\n\n var ebVerkPerKwh =\n a.exp > 0\n ? (\n a.ebVerk /\n a.exp\n )\n : 0;\n\n c.appendChild(\n el(\n 'div',\n 'display:grid;' +\n 'grid-template-columns:repeat(auto-fit,minmax(230px,1fr));' +\n 'gap:12px;',\n\n kolomPrijs(\n 'Afname',\n a.prijsInkBalans,\n EB,\n a.prijsInk,\n D.rood,\n 'Het cijfer van Balans is inclusief BTW en de 2 ct leverkosten. ' +\n 'Daar komt ' +\n eur(\n EB_BASIS,\n 3\n ) +\n ' energiebelasting plus 21% BTW bij, dus ' +\n eur(\n EB,\n 5\n ) +\n '.'\n ) +\n\n kolomPrijs(\n 'Teruglevering',\n a.prijsVerkBalans,\n ebVerkPerKwh,\n a.prijsVerk,\n D.groen,\n\n a.ongesaldeerd > 0.5\n ? (\n kwh(\n a.gesaldeerd\n ) +\n ' van de ' +\n kwh(\n a.exp\n ) +\n ' valt binnen de saldering en krijgt EB terug; ' +\n kwh(\n a.ongesaldeerd\n ) +\n ' niet. Gemiddeld dus minder dan ' +\n eur(\n EB,\n 5\n ) +\n '.'\n )\n : (\n 'Alle teruglevering valt binnen de saldeerruimte, ' +\n 'dus over elke kWh komt ' +\n eur(\n EB,\n 5\n ) +\n ' energiebelasting terug.'\n )\n )\n )\n );\n\n c.appendChild(\n el(\n 'div',\n 'margin-top:12px;' +\n 'padding-top:12px;' +\n 'border-top:1px solid ' +\n D.rand +\n ';display:flex;' +\n 'gap:24px;' +\n 'flex-wrap:wrap;' +\n 'row-gap:12px;',\n\n kolom(\n 'Spread all-in',\n ct(\n a.prijsVerk -\n a.prijsInk\n ) +\n ' / kWh',\n 'verkoop \\u2212 inkoop',\n D.paars\n ) +\n\n kolom(\n 'Energiebelasting',\n eurT(\n a.eb\n ),\n kwh(\n a.imp\n ) +\n ' af, ' +\n kwh(\n a.gesaldeerd\n ) +\n ' terug',\n kleur(\n a.eb\n )\n ) +\n\n kolom(\n 'Netbeheer',\n eurT(\n a.vast\n ),\n 'levering ' +\n eur(\n LEV_DAG,\n 4\n ) +\n ' + transport ' +\n eur(\n TRA_DAG,\n 4\n ) +\n ' \\u2212 vermindering ' +\n eur(\n VER_DAG,\n 4\n ) +\n ' per dag',\n kleur(\n a.vast\n )\n )\n )\n );\n\n return c;\n }\n\n function fprKleur(r) {\n if (r >= 1.6) {\n return D.groen;\n }\n\n if (r >= 1.05) {\n return D.paars;\n }\n\n if (r >= 1.0) {\n return D.oranje;\n }\n\n return D.rood;\n }\n\n function fprWoord(r) {\n if (r >= 1.8) {\n return 'Uitstekend';\n }\n\n if (r >= 1.6) {\n return 'Zeer goed';\n }\n\n if (r >= 1.4) {\n return 'Goed';\n }\n\n if (r >= 1.2) {\n return 'Redelijk';\n }\n\n if (r >= 1.05) {\n return 'Matig';\n }\n\n if (r >= 1.0) {\n return 'Break-even';\n }\n\n return 'Verlies';\n }\n\n function kolom(\n kop,\n groot,\n klein,\n kl\n ) {\n return (\n '
' +\n\n '
' +\n kop +\n '
' +\n\n '
' +\n groot +\n '
' +\n\n '
' +\n klein +\n '
' +\n\n '
'\n );\n }\n\n function bouwFpr(\n f,\n periodeWoord\n ) {\n if (!f) {\n return null;\n }\n\n var c =\n sectie(\n 'be-fpr',\n 'Accu \\u00b7 financial performance ratio'\n );\n\n var kl =\n fprKleur(\n f.ratio\n );\n\n var pct =\n Math.min(\n 100,\n Math.max(\n 0,\n (\n f.ratio /\n 2\n ) *\n 100\n )\n );\n\n c.appendChild(\n el(\n 'div',\n 'display:flex;' +\n 'gap:28px;' +\n 'flex-wrap:wrap;' +\n 'align-items:center;' +\n 'row-gap:18px;',\n\n '
' +\n\n '
' +\n num(\n f.ratio,\n 2\n ) +\n '
' +\n\n '
' +\n\n '
' +\n\n (\n f.ratio >= 1\n ? '+'\n : ''\n ) +\n\n num(\n (\n f.ratio -\n 1\n ) *\n 100,\n 0\n ) +\n\n '% rendement' +\n\n '
' +\n\n '
' +\n fprWoord(\n f.ratio\n ) +\n '
' +\n\n '
' +\n 'waarde ontladen \\u00f7 waarde laden' +\n '
' +\n\n '
' +\n\n '
' +\n\n '
' +\n '
' +\n\n '
' +\n\n kolom(\n 'Laden',\n kwh(\n f.laadKwh\n ),\n ct(\n f.gemLaad\n ) +\n '/kWh',\n D.rood\n ) +\n\n kolom(\n 'Ontladen',\n kwh(\n f.ontlaadKwh\n ),\n ct(\n f.gemOntlaad\n ) +\n '/kWh',\n D.groen\n ) +\n\n kolom(\n 'Netto',\n eurT(\n f.netto\n ),\n num(\n f.retour *\n 100,\n 0\n ) +\n '% retour',\n kleur(\n f.netto\n )\n ) +\n\n kolom(\n 'Spread',\n ct(\n f.gemOntlaad -\n f.gemLaad\n ),\n 'per kWh all-in',\n D.paars\n ) +\n\n '
' +\n\n '
' +\n\n '
' +\n\n '
' +\n '
' +\n\n '
' +\n\n '
' +\n\n '0' +\n '1,0' +\n '2,0+' +\n\n '
' +\n\n '
'\n )\n );\n\n return c;\n }\n\n var KOL1 =\n '92px';\n\n var STAAT_GRID =\n 'grid-template-columns:' +\n KOL1 +\n ' minmax(60px,1fr) 104px 84px 84px 96px;' +\n 'gap:12px;';\n\n function staatKop() {\n return (\n '
' +\n\n '
Maand
' +\n '
' +\n\n '
' +\n 'All-in' +\n '
' +\n\n '
' +\n 'Vschot' +\n '
' +\n\n '
' +\n 'Vaste k.' +\n '
' +\n\n '
' +\n 'Saldo' +\n '
' +\n\n '
'\n );\n }\n\n function staatRij(o) {\n var bg =\n o.nu\n ? 'rgba(107,63,160,.06)'\n : (\n o.even\n ? 'rgba(107,63,160,.02)'\n : 'transparent'\n );\n\n var dim =\n o.geschat\n ? 'opacity:.6;'\n : '';\n\n var cur =\n o.geschat\n ? 'font-style:italic;'\n : '';\n\n var t =\n o.geschat\n ? '~'\n : '';\n\n var balk =\n Math.min(\n 100,\n Math.abs(\n o.stroom\n ) /\n 4\n );\n\n var balkEcht =\n balk;\n\n var balkVoorlopig = 0;\n\n if (\n Number.isFinite(\n o.voorlopigStroom\n ) &&\n Math.abs(\n o.voorlopigStroom\n ) > 0.000001 &&\n Number.isFinite(\n o.stroomEcht\n )\n ) {\n var zelfdeRichting =\n o.stroomEcht === 0 ||\n o.stroom === 0 ||\n (\n o.stroomEcht > 0 &&\n o.stroom > 0\n ) ||\n (\n o.stroomEcht < 0 &&\n o.stroom < 0\n );\n\n if (\n zelfdeRichting &&\n Math.abs(\n o.stroom\n ) >=\n Math.abs(\n o.stroomEcht\n )\n ) {\n balkEcht =\n Math.min(\n balk,\n Math.abs(\n o.stroomEcht\n ) /\n 4\n );\n\n balkVoorlopig =\n Math.max(\n 0,\n balk - balkEcht\n );\n\n } else {\n balkVoorlopig =\n Math.min(\n balk,\n Math.abs(\n o.voorlopigStroom\n ) /\n 4\n );\n\n balkEcht =\n Math.max(\n 0,\n balk - balkVoorlopig\n );\n }\n }\n\n var balkRest = 0;\n\n if (o.naast) {\n var balkProg =\n Math.min(\n 100,\n Math.abs(\n o.naast\n ) /\n 4\n );\n\n balkRest =\n Math.max(\n 0,\n balkProg -\n balk\n );\n }\n\n var vastWeergave =\n -o.vast;\n\n var sub =\n o.sub\n ? (\n '
' +\n o.sub +\n '
'\n )\n : '';\n\n return (\n '
' +\n\n '
' +\n maandLabel(\n o.key\n ) +\n '
' +\n\n '
' +\n\n '
' +\n\n '
0\n ? '4px 0 0 4px'\n : '4px'\n ) +\n ';background:' +\n (\n o.geschat\n ? kleurZacht(\n o.stroom\n )\n : kleur(\n o.stroom\n )\n ) +\n ';\">' +\n '
' +\n\n (\n balkVoorlopig > 0\n ? (\n '
' +\n '
'\n )\n : ''\n ) +\n\n (\n balkRest > 0\n ? (\n '
' +\n '
'\n )\n : ''\n ) +\n\n '
' +\n\n sub +\n\n '
' +\n\n '
' +\n\n t +\n eurCent(\n o.stroom\n ) +\n\n (\n o.naast\n ? (\n '' +\n ' / ~' +\n eurKort(\n o.naast\n ) +\n ''\n )\n : ''\n ) +\n\n '
' +\n\n '
' +\n '+\\u20ac' +\n num(\n o.voorschot,\n 2\n ) +\n '
' +\n\n '
' +\n eurCent(\n vastWeergave\n ) +\n '
' +\n\n '
' +\n t +\n eurKort(\n o.cum\n ) +\n '
' +\n\n '
'\n );\n }\n\n function bouwMaandstaat(\n rijen,\n sald,\n saldProg,\n huidigeKey,\n voorlopigeRijen,\n voorlopigeKey\n ) {\n var c =\n sectie(\n 'be-staat',\n 'Voorschot & verrekening'\n );\n\n var werkelijkPer = {};\n var bekendBedrag = {};\n\n var voorlopigTekst =\n voorlopigeKey\n ? voorlopigeDagenTekst(\n voorlopigeRijen,\n voorlopigeKey\n )\n : '';\n\n var voorlopigStroom = 0;\n var voorlopigAantal = 0;\n\n (voorlopigeRijen || [])\n .forEach(\n function (r) {\n if (\n !r ||\n !r.voorlopig ||\n !voorlopigeKey ||\n String(r.key || '')\n .indexOf(\n voorlopigeKey + '-'\n ) !== 0\n ) {\n return;\n }\n\n var ongesaldeerd =\n naSaldering(\n r.key\n )\n ? r.exp\n : 0;\n\n var p =\n bereken(\n r,\n ongesaldeerd,\n 1\n );\n\n voorlopigStroom +=\n p.stroom;\n\n voorlopigAantal++;\n }\n );\n\n rijen.forEach(\n function (r, i) {\n if (r.leeg) {\n return;\n }\n\n var a =\n bereken(\n r,\n sald[i].ongesaldeerd,\n dagenVerstreken(\n r.key\n )\n );\n\n a.dagenEcht =\n a.dagen;\n\n a.stroomEcht =\n a.stroom;\n\n a.voorlopigStroom =\n 0;\n\n a.voorlopigAantal =\n 0;\n\n if (\n r.key ===\n voorlopigeKey &&\n voorlopigAantal > 0\n ) {\n a.voorlopigStroom =\n voorlopigStroom;\n\n a.voorlopigAantal =\n voorlopigAantal;\n\n a.stroom +=\n voorlopigStroom;\n\n a.vast +=\n voorlopigAantal *\n VAST_DAG;\n\n a.dagen +=\n voorlopigAantal;\n\n a.totaal =\n a.stroom +\n a.vast;\n\n a.saldo =\n a.totaal +\n cfg.voorschot;\n }\n\n werkelijkPer[\n r.key\n ] = a;\n\n if (\n a.dagen >=\n dagenInMaand(\n r.key\n )\n ) {\n bekendBedrag[\n r.key\n ] = a.stroom;\n }\n }\n );\n\n var ank =\n ankers(\n bekendBedrag\n );\n\n var cum = 0;\n var betaald = 0;\n var werkelijk = 0;\n var vastTot = 0;\n var maandenBekend = 0;\n var jaarStroom = 0;\n var jaarVast = 0;\n\n var html =\n staatKop();\n\n rijen.forEach(\n function (r, i) {\n var a =\n werkelijkPer[\n r.key\n ];\n\n var loopt =\n isLopendeMaand(\n r.key\n );\n\n var prog =\n prognoseBedrag(\n r.key,\n ank\n );\n\n if (\n naSaldering(\n r.key\n ) &&\n saldProg[i]\n ) {\n prog -=\n saldProg[i].exp *\n EB;\n }\n\n var stroom;\n var vast;\n var geschat;\n var vs;\n var sub = null;\n var subKleur = null;\n var naast = null;\n\n if (a) {\n geschat =\n false;\n\n stroom =\n a.stroom;\n\n vast =\n a.vast;\n\n vs =\n cfg.voorschot;\n\n betaald +=\n vs;\n\n werkelijk +=\n stroom;\n\n vastTot +=\n vast;\n\n maandenBekend++;\n\n if (loopt) {\n sub =\n 'berekend t/m ' +\n (\n Number.isFinite(\n a.dagenEcht\n )\n ? a.dagenEcht\n : a.dagen\n ) +\n ' ' +\n maandNaam(\n r.key\n ) +\n (\n voorlopigTekst\n ? ' · ' +\n voorlopigTekst\n : ''\n );\n\n if (\n Math.abs(\n stroom\n ) <\n Math.abs(\n prog\n )\n ) {\n naast =\n prog;\n }\n\n } else if (\n a.dagen <\n dagenInMaand(\n r.key\n )\n ) {\n sub =\n 'nog ' +\n (\n dagenInMaand(\n r.key\n ) -\n a.dagen\n ) +\n ' dagen open';\n\n subKleur =\n D.oranje;\n }\n\n if (\n loopt &&\n Math.abs(\n prog\n ) >\n Math.abs(\n stroom\n )\n ) {\n jaarStroom +=\n prog;\n\n jaarVast +=\n dagenInMaand(\n r.key\n ) *\n VAST_DAG;\n\n } else {\n jaarStroom +=\n stroom;\n\n jaarVast +=\n vast;\n }\n\n } else {\n geschat =\n true;\n\n stroom =\n prog;\n\n vast =\n dagenInMaand(\n r.key\n ) *\n VAST_DAG;\n\n vs =\n cfg.voorschot;\n\n jaarStroom +=\n stroom;\n\n jaarVast +=\n vast;\n }\n\n cum +=\n stroom +\n vast +\n vs;\n\n html +=\n staatRij({\n key:\n r.key,\n\n nu:\n r.key ===\n huidigeKey,\n\n even:\n i % 2 === 0,\n\n geschat:\n geschat,\n\n stroom:\n stroom,\n\n stroomEcht:\n a\n ? a.stroomEcht\n : stroom,\n\n voorlopigStroom:\n a\n ? a.voorlopigStroom\n : 0,\n\n vast:\n vast,\n\n voorschot:\n vs,\n\n cum:\n cum,\n\n sub:\n sub,\n\n subKleur:\n subKleur,\n\n naast:\n naast\n });\n }\n );\n\n var jaarVoorschot =\n 12 *\n cfg.voorschot;\n\n var jaarSaldo =\n jaarStroom +\n jaarVast +\n jaarVoorschot;\n\n html +=\n '
' +\n\n '
' +\n 'Prognose jaar' +\n '
' +\n\n '
' +\n\n '
' +\n eurKort(\n jaarStroom\n ) +\n '
' +\n\n '
' +\n '+\\u20ac' +\n num(\n jaarVoorschot,\n 0\n ) +\n '
' +\n\n '
' +\n eurKort(\n -jaarVast\n ) +\n '
' +\n\n '
' +\n eurKort(\n jaarSaldo\n ) +\n '
' +\n\n '
';\n\n var werkelijkTot =\n werkelijk +\n vastTot;\n\n var saldoNu =\n werkelijkTot +\n betaald;\n\n var voortgang =\n contractVoortgang(\n cfg.start,\n new Date()\n );\n\n var pct =\n Math.max(\n 0,\n Math.min(\n 100,\n voortgang.pctExact\n )\n );\n\n var pctTekst =\n (\n Math.round(\n pct * 10\n ) /\n 10\n )\n .toFixed(1)\n .replace('.', ',');\n\n var eind =\n new Date(\n voortgang.grens.getTime()\n );\n\n eind.setDate(\n eind.getDate() - 1\n );\n\n var duurTekst =\n !voortgang.gestart\n ? 'nog niet gestart'\n : voortgang.voltooid\n ? '12 mnd · voltooid'\n : voortgang.maanden +\n ' mnd' +\n (\n voortgang.restDagen > 0\n ? ' + ' +\n voortgang.restDagen +\n (\n voortgang.restDagen === 1\n ? ' dag'\n : ' dgn'\n )\n : ''\n );\n\n c.appendChild(\n el(\n 'div',\n 'display:grid;' +\n 'grid-template-columns:repeat(auto-fit,minmax(170px,1fr));' +\n 'border:1px solid ' +\n D.rand +\n ';border-radius:9px;' +\n 'overflow:hidden;' +\n 'margin-bottom:14px;',\n\n cel(\n 'Betaald t/m nu',\n '+' +\n eur(\n betaald\n ),\n maandenBekend +\n ' \\u00d7 ' +\n eur(\n cfg.voorschot\n ),\n D.inkt\n ) +\n\n cel(\n 'Werkelijk t/m nu',\n eurT(\n werkelijkTot\n ),\n 'stroom + netbeheer + belastingen',\n kleur(\n werkelijkTot\n )\n ) +\n\n cel(\n 'Huidig saldo',\n eurT(\n saldoNu\n ),\n saldoNu < 0\n ? 'nog te betalen'\n : 'te ontvangen',\n kleur(\n saldoNu\n )\n )\n )\n );\n\n c.appendChild(\n el(\n 'div',\n 'margin-bottom:14px;',\n\n '
' +\n\n '' +\n cfg.start.getDate() +\n ' ' +\n MND_LANG[\n cfg.start.getMonth()\n ] +\n ' ' +\n cfg.start.getFullYear() +\n '' +\n\n '' +\n eind.getDate() +\n ' ' +\n MND_LANG[\n eind.getMonth()\n ] +\n ' ' +\n eind.getFullYear() +\n '' +\n\n '
' +\n\n '
' +\n\n '' +\n '\\u25cf nu (' +\n duurTekst +\n ')' +\n '' +\n\n '
' +\n\n '
' +\n\n '
' +\n '
' +\n\n '
' +\n '
' +\n\n '
' +\n\n '
' +\n 'Contractdag ' +\n voortgang.contractDag +\n ' van ' +\n voortgang.totaalDagen +\n ' \\u00b7 ' +\n pctTekst +\n '% van contractjaar' +\n '
'\n )\n );\n\n var det =\n el(\n 'details',\n ''\n );\n\n det.open =\n true;\n\n det.innerHTML =\n '' +\n '\\u25bc Maanddetails verbergen' +\n '' +\n\n '
' +\n html +\n '
' +\n\n '
' +\n 'Cursief met ~ is prognose, geschat uit de eigen geschiedenis. ' +\n 'Arcering in de lopende maand is het voorlopige deel dat al in ' +\n 'het maandresultaat en huidig saldo is meegenomen. ' +\n 'Zodra results-v2-api die dag definitief levert, vervangt de echte data ' +\n 'de provisional automatisch. ' +\n 'Een maand die nog loopt telt in de jaarprognose mee voor ' +\n 'het hoogste van werkelijk en verwacht.' +\n '
';\n\n det.addEventListener(\n 'toggle',\n function () {\n var sum =\n det.querySelector(\n 'summary'\n );\n\n if (sum) {\n sum.textContent =\n det.open\n ? '\\u25bc Maanddetails verbergen'\n : '\\u25b6 Maanddetails tonen';\n }\n }\n );\n\n c.appendChild(\n det\n );\n\n c.dataset.beBetaald =\n String(betaald);\n\n c.dataset.beWerkelijk =\n String(werkelijkTot);\n\n c.dataset.beSaldoNu =\n String(saldoNu);\n\n c.dataset.beMaandenBekend =\n String(maandenBekend);\n\n return c;\n }\n\n function cel(\n kop,\n groot,\n klein,\n kl\n ) {\n return (\n '
' +\n\n '
' +\n kop +\n '
' +\n\n '
' +\n groot +\n '
' +\n\n '
' +\n klein +\n '
' +\n\n '
'\n );\n }\n\n var SALD_GRID =\n 'grid-template-columns:' +\n KOL1 +\n ' 1fr 1fr 1fr 1fr 1.1fr;' +\n 'gap:12px;';\n\n function bouwSaldering(sald) {\n if (!cfg.saldering) {\n return null;\n }\n\n var gevuld =\n sald.filter(\n function (s) {\n return (\n !s.overgeslagen &&\n (\n s.imp > 0 ||\n s.exp > 0\n )\n );\n }\n );\n\n if (!gevuld.length) {\n return null;\n }\n\n var laatsteRij =\n gevuld[\n gevuld.length - 1\n ];\n\n var totImp =\n laatsteRij.cumImp;\n\n var totExp =\n laatsteRij.cumExp;\n\n var over =\n Math.max(\n 0,\n totExp -\n totImp\n );\n\n var ruimte =\n Math.max(\n 0,\n totImp -\n totExp\n );\n\n var naald =\n Math.min(\n 98,\n Math.max(\n 2,\n (\n totImp > 0\n ? totExp /\n totImp\n : 0\n ) *\n 50\n )\n );\n\n var kl =\n over > 0\n ? D.rood\n : D.groen;\n\n var schakelaar =\n '';\n\n var c =\n sectie(\n 'be-sald',\n 'Salderingsbalans contractjaar',\n schakelaar\n );\n\n var tabel =\n '
' +\n\n '
Maand
' +\n\n '
' +\n 'Import' +\n '
' +\n\n '
' +\n 'Export' +\n '
' +\n\n '
' +\n 'Cum. imp' +\n '
' +\n\n '
' +\n 'Cum. exp' +\n '
' +\n\n '
' +\n 'Ongesaldeerd' +\n '
' +\n\n '
';\n\n sald.forEach(\n function (s, i) {\n if (s.overgeslagen) {\n tabel +=\n '
' +\n\n '
' +\n maandLabel(\n s.key\n ) +\n '
' +\n\n '
\\u2013
' +\n '
\\u2013
' +\n '
\\u2013
' +\n '
\\u2013
' +\n '
\\u2013
' +\n\n '
';\n\n return;\n }\n\n var kantel =\n !s.naSaldering &&\n s.ongesaldeerd > 0 &&\n (\n s.cumExp -\n s.exp\n ) <=\n (\n s.cumImp -\n s.imp\n );\n\n var bg =\n s.naSaldering\n ? 'rgba(220,53,69,.05)'\n : (\n kantel\n ? 'rgba(220,53,69,.07)'\n : (\n s.geschat\n ? 'rgba(107,63,160,.03)'\n : (\n i % 2 === 0\n ? 'rgba(107,63,160,.02)'\n : 'transparent'\n )\n )\n );\n\n var stijl =\n s.geschat\n ? 'opacity:.65;font-style:italic;'\n : '';\n\n tabel +=\n '
' +\n\n '
' +\n\n maandLabel(\n s.key\n ) +\n\n (\n kantel\n ? (\n ' ' +\n '\\u25c0' +\n ''\n )\n : ''\n ) +\n\n (\n s.naSaldering\n ? (\n ' ' +\n '\\u26a0' +\n ''\n )\n : ''\n ) +\n\n '
' +\n\n '
' +\n num(\n s.imp,\n 0\n ) +\n '
' +\n\n '
' +\n num(\n s.exp,\n 0\n ) +\n '
' +\n\n '
' +\n num(\n s.cumImp,\n 0\n ) +\n '
' +\n\n '
\n s.cumImp\n ? D.rood\n : D.inkt\n ) +\n ';\">' +\n num(\n s.cumExp,\n 0\n ) +\n '
' +\n\n '
0\n ? '700'\n : '400'\n ) +\n ';color:' +\n (\n s.ongesaldeerd > 0\n ? D.rood\n : D.grijs\n ) +\n ';\">' +\n\n (\n s.ongesaldeerd > 0\n ? (\n num(\n s.ongesaldeerd,\n 0\n ) +\n ' kWh'\n )\n : '\\u2013'\n ) +\n\n '
' +\n\n '
';\n }\n );\n\n c.appendChild(\n el(\n 'div',\n 'display:grid;' +\n 'grid-template-columns:repeat(auto-fit,minmax(150px,1fr));' +\n 'border:1px solid ' +\n D.rand +\n ';border-radius:9px;' +\n 'overflow:hidden;' +\n 'margin-bottom:13px;',\n\n cel(\n 'Import',\n kwh(\n totImp\n ),\n 'contractjaar',\n D.inkt\n ) +\n\n cel(\n 'Export',\n kwh(\n totExp\n ),\n 'contractjaar',\n kl\n ) +\n\n cel(\n over > 0\n ? 'Buiten saldering'\n : 'Saldeerruimte over',\n kwh(\n over > 0\n ? over\n : ruimte\n ),\n over > 0\n ? 'geen EB-teruggave hierop'\n : 'nog te benutten',\n kl\n )\n )\n );\n\n c.appendChild(\n el(\n 'div',\n 'margin-bottom:13px;',\n\n '
' +\n\n '\\u2190 import overheerst' +\n 'break-even' +\n 'export overschot \\u2192' +\n\n '
' +\n\n '
' +\n\n '
' +\n '
' +\n\n '
' +\n '
' +\n\n '
'\n )\n );\n\n if (over > 0) {\n c.appendChild(\n el(\n 'div',\n 'margin-bottom:12px;' +\n 'padding:7px 10px;' +\n 'border-radius:7px;' +\n 'font-size:11px;' +\n 'background:rgba(220,53,69,.06);' +\n 'color:' +\n D.rood +\n ';',\n\n kwh(\n over\n ) +\n ' export valt buiten de saldering \\u2014 daarover komt geen ' +\n eur(\n EB,\n 5\n ) +\n '/kWh energiebelasting terug, dus ' +\n eur(\n over *\n EB\n ) +\n ' minder dan wanneer er ruimte was.'\n )\n );\n }\n\n var det =\n el(\n 'details',\n ''\n );\n\n det.innerHTML =\n '' +\n '\\u25b6 Maand voor maand' +\n '' +\n\n '
' +\n tabel +\n '
' +\n\n '
' +\n\n (\n salderingPrognose\n ? 'Cursief = prognose \\u00b7 '\n : 'Alleen maanden met echte cijfers \\u00b7 '\n ) +\n\n '\\u25c0 kantelpunt saldering \\u00b7 ' +\n '\\u26a0 na 31 december 2026, geen saldering meer' +\n\n '
';\n\n det.addEventListener(\n 'toggle',\n function () {\n var s =\n det.querySelector(\n 'summary'\n );\n\n if (s) {\n s.textContent =\n det.open\n ? '\\u25bc Maand voor maand verbergen'\n : '\\u25b6 Maand voor maand';\n }\n }\n );\n\n c.appendChild(det);\n\n return c;\n }\n\n\n /*\n * ============================================================\n * RESULTATEN — Balans-kaarten en grafiek\n * ============================================================\n */\n\n function zetKaartTekst(\n id,\n tekst,\n kl\n ) {\n var e =\n document.getElementById(\n id\n );\n\n if (!e) {\n return null;\n }\n\n e.textContent =\n tekst;\n\n if (kl) {\n e.style.setProperty(\n 'color',\n kl,\n 'important'\n );\n\n } else {\n e.style.removeProperty(\n 'color'\n );\n }\n\n return e;\n }\n\n function zetSubInfo(\n id,\n html\n ) {\n var e =\n document.getElementById(\n id\n );\n\n if (!e) {\n return;\n }\n\n var houder =\n e.parentElement;\n\n var oud =\n houder.querySelector(\n '.be-sub'\n );\n\n if (oud) {\n oud.remove();\n }\n\n if (!html) {\n return;\n }\n\n houder.appendChild(\n el(\n 'div',\n 'margin-top:6px;' +\n 'font-size:11px;' +\n 'color:' +\n D.grijs +\n ';',\n html\n )\n );\n\n houder.lastElementChild\n .className =\n 'be-sub';\n }\n\n function zetPopover(\n id,\n titel,\n inhoud\n ) {\n var e =\n document.getElementById(\n id\n );\n\n var m =\n e &&\n e.closest(\n '.summary-compact-metric'\n );\n\n var b =\n m &&\n m.querySelector(\n 'button.help-popover'\n );\n\n if (!b) {\n return;\n }\n\n if (\n !b.getAttribute(\n 'data-be-orig-titel'\n )\n ) {\n b.setAttribute(\n 'data-be-orig-titel',\n b.getAttribute(\n 'data-bs-title'\n ) || ''\n );\n\n b.setAttribute(\n 'data-be-orig-inhoud',\n b.getAttribute(\n 'data-bs-content'\n ) || ''\n );\n }\n\n var t =\n titel === null\n ? b.getAttribute(\n 'data-be-orig-titel'\n )\n : titel;\n\n var c =\n inhoud === null\n ? b.getAttribute(\n 'data-be-orig-inhoud'\n )\n : inhoud;\n\n if (\n b.getAttribute(\n 'data-bs-title'\n ) === t &&\n b.getAttribute(\n 'data-bs-content'\n ) === c\n ) {\n return;\n }\n\n b.setAttribute(\n 'data-bs-title',\n t\n );\n\n b.setAttribute(\n 'data-bs-content',\n c\n );\n\n b.setAttribute(\n 'aria-label',\n t\n );\n\n try {\n var BS =\n window.bootstrap;\n\n var inst =\n (\n BS &&\n BS.Popover\n )\n ? BS.Popover.getInstance(\n b\n )\n : null;\n\n if (\n inst &&\n typeof inst.setContent ===\n 'function'\n ) {\n inst.setContent({\n '.popover-header':\n t,\n\n '.popover-body':\n c\n });\n }\n\n } catch (err) {}\n }\n\n function pasPopoversAan(\n a,\n aan\n ) {\n if (!aan) {\n [\n 'summary-stroom-voordeel',\n 'summary-deal-result',\n 'summary-buy-price',\n 'summary-sell-price'\n ].forEach(\n function (id) {\n zetPopover(\n id,\n null,\n null\n );\n }\n );\n\n return;\n }\n\n var ebPerKwhVerk =\n a.exp > 0\n ? (\n a.ebVerk /\n a.exp\n )\n : 0;\n\n var buiten =\n a.ongesaldeerd > 0.5\n ? (\n ' Van de ' +\n kwh(\n a.exp\n ) +\n ' valt ' +\n kwh(\n a.ongesaldeerd\n ) +\n ' buiten de saldeerruimte; ' +\n 'daarover komt geen energiebelasting terug.'\n )\n : (\n ' Alle teruglevering valt binnen de saldeerruimte, ' +\n 'dus over elke kWh komt de volle ' +\n eur(\n EB,\n 5\n ) +\n ' terug.'\n );\n\n zetPopover(\n 'summary-stroom-voordeel',\n 'Kosten stroomafname all-in',\n\n eur(\n Math.abs(\n a.balansInk\n )\n ) +\n ' volgens Balans, inclusief btw en \\u20ac 0,02 leverkosten per kWh. ' +\n 'Daar hoort ' +\n eur(\n Math.abs(\n a.ebInk\n )\n ) +\n ' energiebelasting bij: ' +\n kwh(\n a.imp\n ) +\n ' \\u00d7 ' +\n eur(\n EB,\n 5\n ) +\n ' per kWh. Samen ' +\n eur(\n Math.abs(\n a.allinInk\n )\n ) +\n '. Balans laat die belasting weg, terwijl je hem wel betaalt.'\n );\n\n zetPopover(\n 'summary-deal-result',\n 'Opbrengst teruglevering all-in',\n\n eur(\n a.balansVerk\n ) +\n ' volgens Balans, inclusief btw en minus \\u20ac 0,02 leverkosten per kWh. ' +\n 'Door saldering krijg je daarnaast ' +\n eur(\n a.ebVerk\n ) +\n ' energiebelasting terug over ' +\n kwh(\n a.gesaldeerd\n ) +\n '. Samen ' +\n eur(\n a.allinVerk\n ) +\n '.' +\n buiten\n );\n\n zetPopover(\n 'summary-buy-price',\n 'Gem. inkoopprijs all-in',\n\n ct(\n a.prijsInkBalans\n ) +\n ' per kWh volgens Balans, plus ' +\n ct(\n EB\n ) +\n ' energiebelasting inclusief btw, is ' +\n ct(\n a.prijsInk\n ) +\n ' per kWh. ' +\n 'Dit is het bedrag dat je werkelijk kwijt bent voor een kilowattuur van het net.'\n );\n\n zetPopover(\n 'summary-sell-price',\n 'Gem. verkoopprijs all-in',\n\n ct(\n a.prijsVerkBalans\n ) +\n ' per kWh volgens Balans, plus ' +\n ct(\n ebPerKwhVerk\n ) +\n ' energiebelasting die je via saldering terugkrijgt, is ' +\n ct(\n a.prijsVerk\n ) +\n ' per kWh. ' +\n 'Het verschil met de inkoopprijs is ' +\n ct(\n a.prijsVerk -\n a.prijsInk\n ) +\n ' per kWh.'\n );\n }\n\n function zetKaartLabel(aan) {\n var e =\n document.getElementById(\n 'summary-stroom-voordeel'\n );\n\n if (!e) {\n return;\n }\n\n var kaartEl =\n e.closest(\n '.summary-compact-metric'\n );\n\n var lbl =\n kaartEl\n ? kaartEl.querySelector(\n '.summary-compact-label'\n )\n : null;\n\n if (!lbl) {\n return;\n }\n\n var tekstNode =\n Array.prototype\n .filter.call(\n lbl.childNodes,\n function (n) {\n return (\n n.nodeType === 3\n );\n }\n )[0];\n\n if (tekstNode) {\n tekstNode.nodeValue =\n aan\n ? 'Kosten stroomafname all-in'\n : 'Kosten stroomafname';\n }\n }\n\n function pasBalansKaartenAan(\n a,\n ruw,\n aan\n ) {\n if (!aan) {\n zetKaartTekst(\n 'summary-stroom-voordeel',\n eur(\n ruw.inkoop\n )\n );\n\n zetKaartTekst(\n 'summary-deal-result',\n eur(\n ruw.verkoop\n )\n );\n\n zetKaartTekst(\n 'summary-buy-price',\n prijs(\n ruw.prijsInk\n )\n );\n\n zetKaartTekst(\n 'summary-sell-price',\n prijs(\n ruw.prijsVerk\n )\n );\n\n zetSubInfo(\n 'summary-stroom-voordeel',\n null\n );\n\n zetSubInfo(\n 'summary-deal-result',\n null\n );\n\n zetKaartLabel(\n false\n );\n\n pasPopoversAan(\n a,\n false\n );\n\n return;\n }\n\n zetKaartTekst(\n 'summary-stroom-voordeel',\n eur(\n Math.abs(\n a.allinInk\n )\n ),\n D.rood\n );\n\n zetKaartTekst(\n 'summary-deal-result',\n eur(\n a.allinVerk\n ),\n D.groen\n );\n\n zetKaartTekst(\n 'summary-buy-price',\n prijs(\n a.prijsInk\n )\n );\n\n zetKaartTekst(\n 'summary-sell-price',\n prijs(\n a.prijsVerk\n )\n );\n\n zetSubInfo(\n 'summary-stroom-voordeel',\n\n kwh(\n a.imp\n ) +\n ' \\u00b7 waarvan ' +\n '' +\n eur(\n Math.abs(\n a.ebInk\n )\n ) +\n ' energiebelasting'\n );\n\n zetSubInfo(\n 'summary-deal-result',\n\n kwh(\n a.exp\n ) +\n ' \\u00b7 waarvan ' +\n '' +\n eur(\n a.ebVerk\n ) +\n ' energiebelasting terug'\n );\n\n zetKaartLabel(\n true\n );\n\n pasPopoversAan(\n a,\n true\n );\n }\n\n var chartOrig = null;\n var chartHerstelTimer = null;\n var chartWachtFrame = null;\n var chartWachtStatus = null;\n var chartRenderId = 0;\n\n function grafiekSleutel(chart) {\n return JSON.stringify([\n laatste ? laatste.interval : '',\n laatste ? laatste.periode : '',\n chart.data.labels\n ]);\n }\n\n function grafiekRenderVingerafdruk(chart) {\n if (!chart) {\n return '';\n }\n\n function r(v) {\n return Number.isFinite(v)\n ? Math.round(v * 1000) / 1000\n : null;\n }\n\n var uit = [\n chart.width,\n chart.height,\n chart.data &&\n chart.data.labels\n ? chart.data.labels.length\n : 0\n ];\n\n if (chart.chartArea) {\n uit.push(\n r(chart.chartArea.left),\n r(chart.chartArea.top),\n r(chart.chartArea.right),\n r(chart.chartArea.bottom)\n );\n }\n\n Object.keys(\n chart.scales || {}\n )\n .sort()\n .forEach(\n function (k) {\n var s =\n chart.scales[k];\n\n uit.push(\n k,\n r(s.min),\n r(s.max),\n r(s.left),\n r(s.top),\n r(s.right),\n r(s.bottom)\n );\n }\n );\n\n (chart.data.datasets || [])\n .forEach(\n function (ds, i) {\n var meta =\n chart.getDatasetMeta\n ? chart.getDatasetMeta(i)\n : null;\n\n uit.push(\n ds.label || '',\n Array.isArray(ds.data)\n ? ds.data.length\n : 0,\n meta && meta.hidden\n ? 1\n : 0\n );\n\n (\n meta &&\n Array.isArray(meta.data)\n ? meta.data\n : []\n ).forEach(\n function (e) {\n uit.push(\n r(e.x),\n r(e.y),\n r(e.base),\n r(e.width),\n r(e.height)\n );\n }\n );\n }\n );\n\n return JSON.stringify(\n uit\n );\n }\n\n var CHART_SETS = [\n 'Inkoop kosten',\n 'Verkoop opbrengsten',\n 'Totaal'\n ];\n\n var voorlopigChartPluginGeregistreerd = false;\n\n function zorgVoorlopigChartPlugin() {\n if (\n voorlopigChartPluginGeregistreerd ||\n !window.Chart ||\n typeof Chart.register !==\n 'function'\n ) {\n return;\n }\n\n try {\n Chart.register({\n id:\n 'beVoorlopigArcering',\n\n beforeDatasetsDraw: function (chart) {\n // Alleen bijgetekende staven: 70% van hun oorspronkelijke dekking.\n if (!chart.$beVoorlopigIndices || !chart.$beVoorlopigIndices.length) return;\n chart.data.datasets.forEach(function (ds, di) {\n var meta = chart.getDatasetMeta(di);\n (meta.data || []).forEach(function (bar, index) {\n if (bar.$beOrigineleDraw || typeof bar.draw !== 'function') return;\n var origineel = bar.draw;\n bar.$beOrigineleDraw = origineel;\n bar.draw = function (ctx) {\n ctx.save();\n try {\n if ((chart.$beVoorlopigIndices || []).indexOf(index) !== -1) ctx.globalAlpha *= 0.7;\n return origineel.apply(this, arguments);\n } finally { ctx.restore(); }\n };\n });\n });\n },\n\n afterDatasetsDraw:\n function (chart) {\n var indices =\n chart.$beVoorlopigIndices || [];\n\n if (!indices.length) {\n return;\n }\n\n var ctx =\n chart.ctx;\n\n indices.forEach(\n function (idx) {\n chart.data.datasets\n .forEach(\n function (ds, datasetIndex) {\n if (\n !Array.isArray(\n ds.data\n ) ||\n ds.data[idx] == null ||\n !Number.isFinite(\n Number(\n ds.data[idx]\n )\n )\n ) {\n return;\n }\n\n var meta =\n chart.getDatasetMeta(\n datasetIndex\n );\n\n var staaf =\n meta &&\n meta.data &&\n meta.data[idx];\n\n if (\n !staaf ||\n !Number.isFinite(\n staaf.x\n ) ||\n !Number.isFinite(\n staaf.y\n ) ||\n !Number.isFinite(\n staaf.base\n ) ||\n !Number.isFinite(\n staaf.width\n ) ||\n staaf.width <= 0\n ) {\n return;\n }\n\n var links =\n staaf.x -\n staaf.width / 2;\n\n var rechts =\n staaf.x +\n staaf.width / 2;\n\n var boven =\n Math.min(\n staaf.y,\n staaf.base\n );\n\n var onder =\n Math.max(\n staaf.y,\n staaf.base\n );\n\n if (\n onder - boven < 1\n ) {\n return;\n }\n\n ctx.save();\n\n ctx.beginPath();\n ctx.rect(\n links,\n boven,\n rechts - links,\n onder - boven\n );\n ctx.clip();\n\n ctx.fillStyle =\n 'rgba(107,63,160,.035)';\n\n ctx.fillRect(\n links,\n boven,\n rechts - links,\n onder - boven\n );\n\n ctx.strokeStyle =\n 'rgba(107,63,160,.28)';\n\n ctx.lineWidth =\n 1;\n\n var hoogte =\n onder - boven;\n\n for (\n var p =\n links - hoogte;\n p < rechts;\n p += 7\n ) {\n ctx.beginPath();\n ctx.moveTo(\n p,\n onder\n );\n ctx.lineTo(\n p + hoogte,\n boven\n );\n ctx.stroke();\n }\n\n ctx.restore();\n }\n );\n }\n );\n }\n });\n\n voorlopigChartPluginGeregistreerd =\n true;\n\n } catch (e) {\n /*\n * Chart kan de plugin al kennen na een SPA-herbouw.\n */\n voorlopigChartPluginGeregistreerd =\n true;\n }\n }\n\n function herstelVoorlopigeGrafiek(chart) {\n var v = chart && chart.$beVoorlopig;\n if (!v) return;\n if (JSON.stringify(chart.data.labels) === JSON.stringify(v.projectedLabels)) {\n chart.data.labels = v.basisLabels.slice();\n v.datasets.forEach(function (o) {\n if (chart.data.datasets.indexOf(o.ref) !== -1) o.ref.data = o.data.slice();\n });\n }\n chart.$beVoorlopig = null;\n chart.$beVoorlopigIndices = [];\n }\n\n function pasVoorlopigeMaandGrafiekAan(chart, rijen, allin) {\n if (!chart || !laatste || laatste.interval !== 'month') return;\n var mk = laatste.periode;\n if (!/^\\d{4}-\\d{2}$/.test(String(mk))) return;\n var echt = laatste.rijen || [];\n var labelsBron = chart.data.labels || [];\n function labelKey(lbl) {\n if (typeof lbl === 'number' && lbl >= 1 && lbl <= 31) return mk + '-' + n2(lbl);\n var t = String(lbl).trim().toLowerCase().replace(/^~/, '');\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(t)) return t.slice(0, 7) === mk ? t : null;\n var m = t.match(/^(\\d{1,2})(?:\\s+([a-z]+)\\.?)?$/);\n if (!m || +m[1] < 1 || +m[1] > dagenInMaand(mk)) return null;\n if (m[2] && m[2].slice(0, 3) !== MND_KORT[+mk.slice(5) - 1].toLowerCase().slice(0, 3)) return null;\n return mk + '-' + n2(+m[1]);\n }\n var indices = {};\n labelsBron.forEach(function (lbl, i) {\n var k = labelKey(lbl);\n if (k) indices[k] = indices[k] === undefined ? i : -1;\n });\n var perDag = {}, dubbel = false;\n echt.forEach(function (r, i) {\n if (!r || r.bronAanwezig === false) return;\n if (!voorlopigeDagGrenzen(r.key) || r.key.slice(0, 7) !== mk || perDag[r.key]) {\n dubbel = true; return;\n }\n var idx = indices[r.key];\n if (idx === undefined && echt.length === labelsBron.length) idx = i;\n if (idx === undefined || idx < 0) { dubbel = true; return; }\n perDag[r.key] = { index: idx };\n });\n if (dubbel) return;\n var opslag = leesVoorlopigeDagen();\n (rijen || []).forEach(function (r) {\n if (r && r.key && r.key.slice(0, 7) === mk && opslag[r.key] && !perDag[r.key]) {\n perDag[r.key] = { rij: opslag[r.key] };\n }\n });\n var basisLabels = chart.data.labels.slice();\n var datasets = chart.data.datasets.map(function (ds) {\n return { ref: ds, data: Array.isArray(ds.data) ? ds.data.slice() : [] };\n });\n var vandaag = iso(new Date());\n var einde = mk === vandaag.slice(0, 7) ? +vandaag.slice(8) : dagenInMaand(mk);\n if (mk > vandaag.slice(0, 7)) return;\n var labels = [], indices = [];\n var nieuw = datasets.map(function () { return []; });\n for (var dag = 1; dag <= einde; dag++) {\n var key = mk + '-' + n2(dag), item = perDag[key];\n labels.push(item && item.index !== undefined\n ? basisLabels[item.index]\n : item && item.rij\n ? voorlopigDagLabel(key)\n : key);\n var a = item && item.rij ? bereken(item.rij, naSaldering(key) ? item.rij.exp : 0, 1) : null;\n if (a) indices.push(dag - 1);\n datasets.forEach(function (o, di) {\n var value = null;\n if (item && item.index !== undefined) {\n value = o.data[item.index] == null ? null : o.data[item.index];\n } else if (a) {\n if (o.ref.label === 'Inkoop kosten') value = allin ? -a.allinInk : item.rij.inkoop;\n if (o.ref.label === 'Verkoop opbrengsten') value = allin ? a.allinVerk : item.rij.verkoop;\n if (o.ref.label === 'Totaal') value = allin ? a.stroom : -item.rij.inkoop + item.rij.verkoop;\n if (o.ref.label === 'Energiebelasting') value = allin ? a.eb : null;\n }\n nieuw[di].push(value);\n });\n }\n chart.data.labels = labels;\n datasets.forEach(function (o, i) { o.ref.data = nieuw[i]; });\n chart.$beVoorlopig = { basisLabels: basisLabels, datasets: datasets, projectedLabels: labels.slice() };\n chart.$beVoorlopigIndices = indices;\n zorgVoorlopigChartPlugin();\n }\n\n function pasGrafiekAan(perPeriode, aan, poging, renderId, bron, voorlopigeRijen) {\n poging = poging || 0;\n\n if (\n renderId !== chartRenderId ||\n laatste !== bron\n ) {\n return;\n }\n\n var chart =\n window.Chart &&\n Chart.getChart\n ? Chart.getChart(\n 'summary-chart'\n )\n : null;\n\n /*\n * Alleen de Resultaten-chart wordt hier gevolgd.\n * Actueel en andere Chart.js-instanties worden nergens aangeraakt.\n */\n if (!chart) {\n if (poging < 30) {\n chartHerstelTimer =\n setTimeout(\n function () {\n pasGrafiekAan(\n perPeriode,\n aan,\n poging + 1,\n renderId,\n bron,\n voorlopigeRijen\n );\n },\n 75\n );\n }\n\n return;\n }\n\n /*\n * Een eventuele vorige maandprojectie één keer terugzetten naar\n * de basislabels/data voordat we op de native Balans-render wachten.\n *\n * Geen chart.update() hier: Balans mag eerst zijn eigen render en\n * animatie volledig afmaken.\n */\n if (\n chart.$beHerstelRenderId !==\n renderId\n ) {\n herstelVoorlopigeGrafiek(\n chart\n );\n\n chart.$beHerstelRenderId =\n renderId;\n }\n\n var n =\n chart.data &&\n chart.data.labels\n ? chart.data.labels.length\n : 0;\n\n var origineleSets =\n chart.data &&\n chart.data.datasets\n ? chart.data.datasets.filter(\n function (d) {\n return (\n CHART_SETS.indexOf(\n d.label\n ) !== -1\n );\n }\n )\n : [];\n\n var klaar =\n n > 0 &&\n origineleSets.length ===\n CHART_SETS.length &&\n origineleSets.every(\n function (d) {\n return (\n Array.isArray(\n d.data\n ) &&\n d.data.length === n\n );\n }\n );\n\n if (!klaar) {\n if (poging < 30) {\n chartHerstelTimer =\n setTimeout(\n function () {\n pasGrafiekAan(\n perPeriode,\n aan,\n poging + 1,\n renderId,\n bron,\n voorlopigeRijen\n );\n },\n 75\n );\n }\n\n return;\n }\n\n var nu =\n (\n window.performance &&\n typeof performance.now ===\n 'function'\n )\n ? performance.now()\n : Date.now();\n\n var vinger =\n grafiekRenderVingerafdruk(\n chart\n );\n\n if (\n !chartWachtStatus ||\n chartWachtStatus.renderId !==\n renderId ||\n chartWachtStatus.chart !==\n chart\n ) {\n chartWachtStatus = {\n renderId:\n renderId,\n\n chart:\n chart,\n\n gestart:\n nu,\n\n vorige:\n vinger,\n\n stabiel:\n 0\n };\n\n } else if (\n chartWachtStatus.vorige ===\n vinger\n ) {\n chartWachtStatus.stabiel++;\n\n } else {\n chartWachtStatus.vorige =\n vinger;\n\n chartWachtStatus.stabiel =\n 0;\n }\n\n /*\n * Dit is bewust géén vaste \"180 ms en hopen\"-render meer.\n *\n * We kijken uitsluitend naar summary-chart en wachten:\n * - minimaal 450 ms nadat de native chart bruikbaar werd;\n * - én minstens 5 opeenvolgende animation frames zonder\n * verandering in schaal/chartArea/staaf- of puntgeometrie.\n *\n * Daardoor kan de originele Balans-animatie volledig aflopen.\n * Na maximaal 3 s gaan we toch door om een vastloper te voorkomen.\n */\n var langGenoeg =\n nu -\n chartWachtStatus.gestart >=\n 450;\n\n var stabielGenoeg =\n chartWachtStatus.stabiel >=\n 5;\n\n var timeout =\n nu -\n chartWachtStatus.gestart >=\n 3000;\n\n if (\n (\n !langGenoeg ||\n !stabielGenoeg\n ) &&\n !timeout\n ) {\n chartWachtFrame =\n requestAnimationFrame(\n function () {\n pasGrafiekAan(\n perPeriode,\n aan,\n poging,\n renderId,\n bron,\n voorlopigeRijen\n );\n }\n );\n\n return;\n }\n\n chartWachtFrame =\n null;\n\n chartWachtStatus =\n null;\n\n delete chart.$beHerstelRenderId;\n\n /*\n * Pas NU — nadat Balans klaar is — nemen we de native reeks als\n * basis en zetten we de All-in/provisional projectie erop.\n */\n var sleutel =\n grafiekSleutel(\n chart\n );\n\n if (\n !chartOrig ||\n chartOrig.sleutel !==\n sleutel ||\n chartOrig.chart !==\n chart\n ) {\n chartOrig = {\n chart:\n chart,\n\n sleutel:\n sleutel,\n\n n:\n n,\n\n sets:\n origineleSets.map(\n function (d) {\n return {\n label:\n d.label,\n\n data:\n d.data.slice()\n };\n }\n )\n };\n }\n\n chart.data.datasets =\n chart.data.datasets\n .filter(\n function (d) {\n return (\n d.label !==\n 'Energiebelasting'\n );\n }\n );\n\n if (\n !aan ||\n !perPeriode ||\n perPeriode.length !== n\n ) {\n chartOrig.sets\n .forEach(\n function (o) {\n var ds =\n chart.data.datasets\n .find(\n function (d) {\n return (\n d.label ===\n o.label\n );\n }\n );\n\n if (ds) {\n ds.data =\n o.data.slice();\n }\n }\n );\n\n } else {\n chart.data.datasets\n .forEach(\n function (ds) {\n if (\n ds.label ===\n 'Inkoop kosten'\n ) {\n ds.data =\n perPeriode.map(\n function (a) {\n return -a.allinInk;\n }\n );\n }\n\n if (\n ds.label ===\n 'Verkoop opbrengsten'\n ) {\n ds.data =\n perPeriode.map(\n function (a) {\n return a.allinVerk;\n }\n );\n }\n\n if (\n ds.label ===\n 'Totaal'\n ) {\n ds.data =\n perPeriode.map(\n function (a) {\n return a.stroom;\n }\n );\n }\n }\n );\n\n chart.data.datasets.push({\n label:\n 'Energiebelasting',\n\n data:\n perPeriode.map(\n function (a) {\n return a.eb;\n }\n ),\n\n backgroundColor:\n '#1D9E75',\n\n borderRadius:\n 0,\n\n order:\n 2\n });\n }\n\n /*\n * Provisional dagen veranderen bij Maand ook het aantal X-categorieën.\n * Dat gebeurt vóór onderstaande normale Chart.js-update, zodat breedte\n * en schaal in dezelfde layoutpass opnieuw worden berekend.\n */\n pasVoorlopigeMaandGrafiekAan(\n chart,\n voorlopigeRijen || [],\n !!aan\n );\n\n if (\n chart.options.scales &&\n chart.options.scales.y\n ) {\n chart.options.scales.y.min =\n undefined;\n\n chart.options.scales.y.max =\n undefined;\n }\n\n /*\n * Geen update('none'): de normale Chart.js-animatie blijft behouden.\n * Omdat de native animatie hierboven al klaar was, vechten twee renders\n * niet meer om dezelfde schaal/bar-geometrie.\n */\n chart.update();\n }\n\n\n /*\n * ============================================================\n * RESULTATEN — API onderscheppen\n * ============================================================\n */\n\n var laatste = null;\n var periodeCache = {};\n var timer = null;\n\n function isJaarInterval(interval) {\n return interval === 'year' || interval === 'rolling_year';\n }\n\n function actievePeriode() {\n var knop = document.querySelector('.summary-granularity.active');\n var g = knop ? knop.textContent.trim().toLowerCase() : 'maand';\n\n if (g === 'dag') {\n var dagPicker = document.getElementById('dayPicker');\n return {\n interval: 'day',\n periode: dagPicker ? dagPicker.value : null\n };\n }\n\n if (g === 'jaar') {\n var jaarPicker = document.getElementById('summary-year-picker');\n var jaarKeuze = jaarPicker ? String(jaarPicker.value || '') : '';\n var jaarTekst =\n jaarPicker &&\n jaarPicker.options &&\n jaarPicker.selectedIndex >= 0\n ? String(\n jaarPicker.options[\n jaarPicker.selectedIndex\n ].textContent || ''\n ).trim()\n : (\n jaarPicker\n ? String(\n jaarPicker.textContent ||\n jaarPicker.getAttribute('value') ||\n jaarPicker.getAttribute('aria-valuetext') ||\n ''\n ).trim()\n : ''\n );\n\n /*\n * Balans gebruikt voor \"12 maanden\" sinds de dashboardwijziging:\n * interval=rolling_year&period=de huidige jjjj-mm.\n */\n if (\n jaarKeuze === 'rolling_year' ||\n /12\\s*maanden/i.test(jaarTekst)\n ) {\n return {\n interval: 'rolling_year',\n periode: iso(new Date()).slice(0, 7)\n };\n }\n\n return {\n interval: 'year',\n periode: jaarKeuze || String(new Date().getFullYear())\n };\n }\n\n var maandPicker = document.getElementById('summary-month-picker');\n return {\n interval: 'month',\n periode: maandPicker ? maandPicker.value : null\n };\n }\n\n function periodeSleutel(\n interval,\n periode\n ) {\n return (\n String(interval || '') +\n '|' +\n String(periode || '')\n );\n }\n\n function pastBijActievePeriode(\n data,\n actief\n ) {\n if (!data || !actief) {\n return false;\n }\n\n return (\n data.interval === actief.interval &&\n (\n !actief.periode ||\n String(data.periode || '') === String(actief.periode)\n )\n );\n }\n\n function kiesActieveCache() {\n var actief = actievePeriode();\n var opgeslagen = periodeCache[\n periodeSleutel(\n actief.interval,\n actief.periode\n )\n ];\n\n if (opgeslagen) {\n laatste = opgeslagen;\n return true;\n }\n\n return pastBijActievePeriode(\n laatste,\n actief\n );\n }\n\n function onthoud(\n j,\n valInterval,\n valPeriode\n ) {\n if (\n !j ||\n !Array.isArray(\n j.series\n )\n ) {\n return false;\n }\n\n var actief =\n actievePeriode();\n\n var isAangevraagdeRolling =\n actief.interval === 'rolling_year' &&\n valInterval === 'rolling_year';\n\n var interval =\n isAangevraagdeRolling\n ? 'rolling_year'\n : (\n j.interval ||\n valInterval ||\n 'month'\n );\n\n var pv =\n j.pricing || {};\n\n var tot =\n j.totals || {};\n\n var snapshot = {\n rijen:\n j.series.map(\n function (s) {\n return naarIncl(\n s,\n pv\n );\n }\n ),\n\n totaal:\n naarIncl(\n tot,\n pv\n ),\n\n ruw: {\n inkoop:\n getal(\n tot.total_buy_eur\n ),\n\n verkoop:\n getal(\n tot.total_sell_eur\n ),\n\n prijsInk:\n getal(\n tot.avg_buy_price_eur_per_kwh\n ),\n\n prijsVerk:\n getal(\n tot.avg_sell_price_eur_per_kwh\n )\n },\n\n interval:\n interval,\n\n periode:\n isAangevraagdeRolling\n ? (\n valPeriode ||\n actief.periode\n )\n : (\n j.period ||\n valPeriode ||\n actief.periode\n )\n };\n\n periodeCache[\n periodeSleutel(\n snapshot.interval,\n snapshot.periode\n )\n ] = snapshot;\n\n /*\n * Balans kan tijdens de opbouw van Resultaten nog een antwoord van\n * de vorige granulariteit afronden. Bewaar dat antwoord wel, maar laat\n * het de nu gekozen Dag/Maand/Jaar-periode nooit overschrijven.\n */\n if (!pastBijActievePeriode(snapshot, actief)) {\n return false;\n }\n\n laatste = snapshot;\n\n return true;\n }\n\n if (\n KLANT &&\n KLANT !== 'account'\n ) {\n window.fetch =\n function (\n input,\n init\n ) {\n var url =\n typeof input === 'string'\n ? input\n : (\n input &&\n typeof input.url ===\n 'string'\n ? input.url\n : ''\n );\n\n var p =\n origFetch(\n input,\n init\n );\n\n // Observe dashboard price refreshes immediately, without changing its response.\n if (url.indexOf('/customer/current-prices/api/') !== -1) {\n p.then(function (resp) {\n if (!resp.ok) return;\n return resp.clone().json().then(function (j) {\n if (isActueelPagina()) beOntvangLivePrijzen(j);\n beBewaarPrijzenLater(j);\n });\n }).catch(function () {});\n }\n\n if (\n url.indexOf(\n 'results-v2-api'\n ) === -1\n ) {\n return p;\n }\n\n return p.then(\n function (resp) {\n if (!resp.ok) {\n return resp;\n }\n\n var requestUrl;\n\n try {\n requestUrl =\n new URL(\n url,\n location.origin\n );\n } catch (e) {\n requestUrl = null;\n }\n\n resp.clone()\n .json()\n .then(\n function (j) {\n if (\n onthoud(\n j,\n requestUrl\n ? requestUrl.searchParams.get('interval')\n : null,\n requestUrl\n ? requestUrl.searchParams.get('period')\n : null\n )\n ) {\n plan();\n }\n }\n )\n .catch(\n function () {}\n );\n\n return resp;\n }\n );\n };\n }\n\n function plan() {\n clearTimeout(\n timer\n );\n\n timer =\n setTimeout(\n function () {\n if (kiesActieveCache()) {\n herteken();\n\n } else {\n zelfHalen()\n .then(\n function (ok) {\n if (ok) {\n herteken();\n }\n }\n );\n }\n },\n 90\n );\n }\n\n var bezig = false;\n\n function zelfHalen() {\n if (\n bezig ||\n !KLANT\n ) {\n return Promise.resolve(\n false\n );\n }\n\n var actief =\n actievePeriode();\n\n if (!actief.periode) {\n return Promise.resolve(\n false\n );\n }\n\n bezig = true;\n\n var u =\n new URL(\n location.origin +\n '/customer/' +\n KLANT +\n '/results-v2-api/'\n );\n\n u.searchParams.set(\n 'interval',\n actief.interval\n );\n\n u.searchParams.set(\n 'period',\n actief.periode\n );\n\n u.searchParams.set(\n 'include_vat',\n '1'\n );\n\n u.searchParams.set(\n 'include_delivery_cost',\n '1'\n );\n\n return origFetch(\n u,\n {\n credentials:\n 'same-origin'\n }\n )\n\n .then(\n function (r) {\n return r.ok\n ? r.json()\n : null;\n }\n )\n\n .then(\n function (j) {\n bezig = false;\n\n return onthoud(\n j,\n actief.interval,\n actief.periode\n );\n }\n )\n\n .catch(\n function () {\n bezig = false;\n return false;\n }\n );\n }\n\n\n /*\n * ============================================================\n * RESULTATEN — pagina bouwen\n * ============================================================\n */\n\n function anker() {\n var k =\n document.querySelector(\n '.results-summary-card'\n );\n\n return k\n ? k.closest(\n '.row'\n )\n : null;\n }\n\n function houder() {\n var h =\n document.getElementById(\n 'be-panelen'\n );\n\n if (h) {\n return h;\n }\n\n var a =\n anker();\n\n if (\n !a ||\n !a.parentElement\n ) {\n return null;\n }\n\n h =\n el(\n 'div',\n ''\n );\n\n h.id =\n 'be-panelen';\n\n a.parentElement\n .insertBefore(\n h,\n a.nextSibling\n );\n\n return h;\n }\n\n function regelBalansBlok() {\n var b =\n document.querySelector(\n '.results-trade-savings-card'\n );\n\n /*\n * De nieuwe dashboardopbouw deelt layout-rijen met andere onderdelen.\n * Verberg daarom alleen de kaart zelf en nooit meer closest('.row').\n */\n if (b) {\n b.style.display =\n cfg.balansBlok\n ? ''\n : 'none';\n }\n\n /* De originele kop/intro blijft staan; die hoort bij de pagina. */\n }\n\n function periodeNaam() {\n var t =\n document.getElementById(\n 'summary-title'\n );\n\n return t\n ? t.textContent.trim()\n : (\n laatste\n ? laatste.periode\n : ''\n );\n }\n\n function herteken() {\n if (!kiesActieveCache()) {\n zelfHalen()\n .then(\n function (ok) {\n if (ok) {\n herteken();\n }\n }\n );\n\n return;\n }\n\n var h =\n houder();\n\n if (!h) {\n return;\n }\n\n regelBalansBlok();\n var renderBron = laatste;\n\n var extraMaandKey =\n null;\n\n if (\n laatste.interval ===\n 'day' &&\n laatste.periode\n ) {\n extraMaandKey =\n String(\n laatste.periode\n ).slice(\n 0,\n 7\n );\n\n } else if (\n isJaarInterval(laatste.interval) &&\n (\n laatste.interval === 'rolling_year' ||\n String(laatste.periode) === String(new Date().getFullYear())\n )\n ) {\n extraMaandKey =\n iso(\n new Date()\n ).slice(\n 0,\n 7\n );\n }\n\n var extraMaand =\n extraMaandKey\n ? haalMaandDagen(\n extraMaandKey\n )\n : Promise.resolve(\n []\n );\n\n /*\n * Voorschot & verrekening beslaat het contractjaar, onafhankelijk\n * van de hierboven geselecteerde grafiekperiode. Valideer de\n * voorlopige dagen daarom altijd tegen de echte dagen van de\n * lopende maand, ook wanneer de gebruiker 2025 bekijkt.\n */\n var contractVoorlopigeKey =\n iso(new Date()).slice(0, 7);\n\n var contractMaand =\n laatste.interval === 'month' &&\n laatste.periode === contractVoorlopigeKey\n ? Promise.resolve(laatste.rijen)\n : extraMaandKey === contractVoorlopigeKey\n ? extraMaand\n : haalMaandDagen(contractVoorlopigeKey);\n\n Promise.all([\n haalContractjaar(),\n haalLopendeDagen(),\n extraMaand,\n contractMaand\n ]).then(\n function (res) {\n if (laatste !== renderBron) return;\n var jaar =\n res[0];\n\n var sald =\n salderingReeks(\n jaar,\n false\n );\n\n var saldProg =\n salderingReeks(\n jaar,\n true\n );\n\n var tot =\n laatste.totaal;\n\n var huidigeKey = null;\n var ongesaldeerd;\n var dagen;\n var periodeWoord;\n\n if (\n laatste.interval ===\n 'month' &&\n laatste.periode\n ) {\n huidigeKey =\n laatste.periode;\n\n tot.key =\n huidigeKey;\n\n var s =\n sald.find(\n function (x) {\n return (\n x.key ===\n huidigeKey\n );\n }\n );\n\n ongesaldeerd =\n s\n ? s.ongesaldeerd\n : Math.max(\n 0,\n tot.exp -\n tot.imp\n );\n\n dagen =\n dagenVerstreken(\n huidigeKey\n );\n\n periodeWoord =\n 'deze maand';\n\n } else if (\n laatste.interval ===\n 'day'\n ) {\n huidigeKey =\n laatste.periode\n ? String(\n laatste.periode\n ).slice(\n 0,\n 7\n )\n : null;\n\n var sm =\n sald.find(\n function (x) {\n return (\n x.key ===\n huidigeKey\n );\n }\n );\n\n var maandExp =\n res[2].reduce(\n function (\n a,\n r\n ) {\n return (\n a +\n r.exp\n );\n },\n 0\n );\n\n var fractieDagOnges =\n (\n sm &&\n maandExp > 0\n )\n ? (\n sm.ongesaldeerd /\n maandExp\n )\n : 0;\n\n ongesaldeerd =\n tot.exp *\n Math.min(\n 1,\n Math.max(\n 0,\n fractieDagOnges\n )\n );\n\n dagen = 1;\n\n periodeWoord =\n 'deze dag';\n\n } else {\n if (\n isJaarInterval(laatste.interval) &&\n (\n laatste.interval === 'rolling_year' ||\n String(laatste.periode) === String(new Date().getFullYear())\n )\n ) {\n huidigeKey =\n iso(\n new Date()\n ).slice(\n 0,\n 7\n );\n }\n\n var eigenKeys = {};\n\n laatste.rijen\n .forEach(\n function (r) {\n if (r.key) {\n eigenKeys[\n r.key\n ] = true;\n }\n }\n );\n\n dagen =\n laatste.rijen\n .reduce(\n function (\n a,\n r\n ) {\n return (\n a +\n (\n r.key\n ? dagenVerstreken(\n r.key\n )\n : 0\n )\n );\n },\n 0\n );\n\n ongesaldeerd =\n sald.reduce(\n function (\n a,\n x\n ) {\n return (\n a +\n (\n x.overgeslagen ||\n !eigenKeys[\n x.key\n ]\n ? 0\n : x.ongesaldeerd\n )\n );\n },\n 0\n );\n\n periodeWoord =\n 'deze periode';\n }\n\n var a =\n bereken(\n tot,\n ongesaldeerd,\n dagen\n );\n\n var fractieOnges =\n tot.exp > 0\n ? (\n ongesaldeerd /\n tot.exp\n )\n : 0;\n\n var perPeriode =\n laatste.rijen.map(\n function (r) {\n return bereken(\n r,\n r.exp *\n fractieOnges,\n 1\n );\n }\n );\n\n var voorlopigeRijen =\n (\n laatste.interval ===\n 'month' ||\n laatste.interval ===\n 'day'\n )\n ? voorlopigeDagenVoorMaand(\n huidigeKey,\n laatste.interval ===\n 'day'\n ? res[2]\n : laatste.rijen\n )\n : (\n isJaarInterval(laatste.interval) &&\n huidigeKey\n ? voorlopigeDagenVoorMaand(\n huidigeKey,\n res[2]\n )\n : []\n );\n\n var contractVoorlopigeRijen =\n voorlopigeDagenVoorMaand(\n contractVoorlopigeKey,\n res[3]\n );\n\n pasBalansKaartenAan(\n a,\n laatste.ruw,\n cfg.allin\n );\n\n clearTimeout(\n chartHerstelTimer\n );\n\n if (chartWachtFrame) {\n cancelAnimationFrame(\n chartWachtFrame\n );\n\n chartWachtFrame =\n null;\n }\n\n chartWachtStatus =\n null;\n\n var renderId =\n ++chartRenderId;\n\n var bron =\n laatste;\n\n var grafiekAan =\n cfg.allin &&\n laatste.interval !==\n 'day';\n\n chartHerstelTimer =\n setTimeout(\n function () {\n pasGrafiekAan(\n perPeriode,\n grafiekAan,\n 0,\n renderId,\n bron,\n voorlopigeRijen\n );\n },\n 30\n );\n\n h.innerHTML = '';\n\n if (!cfg.allin) {\n h.appendChild(\n el(\n 'div',\n 'margin-top:14px;' +\n 'padding:11px 14px;' +\n 'border:1px dashed ' +\n D.rand +\n ';border-radius:9px;' +\n 'font-size:12px;' +\n 'color:' +\n D.grijs +\n ';',\n\n 'All-in staat uit \\u2014 dit zijn de cijfers van Balans zelf, ' +\n 'zonder energiebelasting. Zet de schakelaar ' +\n 'All-in in de kaartkop aan.'\n )\n );\n\n return;\n }\n\n var maandStaat =\n bouwMaandstaat(\n jaar,\n sald,\n saldProg,\n huidigeKey,\n contractVoorlopigeRijen,\n contractVoorlopigeKey\n );\n\n var kaartCtx = {\n naam:\n periodeNaam(),\n\n interval:\n laatste.interval,\n\n periodeWoord:\n periodeWoord\n };\n\n if (\n laatste.interval ===\n 'rolling_year'\n ) {\n kaartCtx.contractBetaald =\n parseFloat(\n maandStaat.dataset.beBetaald\n );\n\n kaartCtx.contractWerkelijk =\n parseFloat(\n maandStaat.dataset.beWerkelijk\n );\n\n kaartCtx.contractSaldo =\n parseFloat(\n maandStaat.dataset.beSaldoNu\n );\n\n kaartCtx.contractMaanden =\n parseInt(\n maandStaat.dataset.beMaandenBekend,\n 10\n ) || 0;\n }\n\n if (laatste.interval === 'month') {\n kaartCtx.voorlopigStroom = 0;\n kaartCtx.voorlopigAantal = voorlopigeRijen.length;\n voorlopigeRijen.forEach(function (r) {\n kaartCtx.voorlopigStroom += bereken(\n r, naSaldering(r.key) ? r.exp : 0, 1\n ).stroom;\n });\n }\n\n var kaartEl =\n bouwHoofdkaart(\n a,\n kaartCtx\n );\n\n kaartEl.appendChild(\n bouwPrijsopbouw(\n a\n )\n );\n\n if (laatste.interval !== 'day') {\n var f = fpr(perPeriode);\n var fprSectie = bouwFpr(f, periodeWoord);\n\n if (fprSectie) {\n kaartEl.appendChild(fprSectie);\n }\n }\n\n kaartEl.appendChild(\n maandStaat\n );\n\n var saldSectie =\n bouwSaldering(\n salderingPrognose\n ? saldProg\n : sald\n );\n\n if (saldSectie) {\n kaartEl.appendChild(\n saldSectie\n );\n }\n\n h.appendChild(\n kaartEl\n );\n\n var knop =\n document.getElementById(\n 'be-sald-prog'\n );\n\n if (knop) {\n knop.addEventListener(\n 'click',\n function (e) {\n e.preventDefault();\n\n salderingPrognose =\n !salderingPrognose;\n\n herteken();\n }\n );\n }\n\n var g =\n grenzen();\n\n if (\n g &&\n g.max_day\n ) {\n h.appendChild(\n bouwVerwerkt(\n g\n )\n );\n }\n }\n );\n }\n\n function bouwVerwerkt(g) {\n var laatsteDag =\n new Date(\n g.max_day +\n 'T12:00:00'\n );\n\n var achter =\n Math.round(\n (\n new Date(\n iso(\n new Date()\n ) +\n 'T12:00:00'\n ) -\n laatsteDag\n ) /\n 86400000\n );\n\n var laat =\n achter > 3;\n\n var c =\n el(\n 'div',\n 'margin-top:14px;' +\n 'padding:9px 13px;' +\n 'border-radius:8px;' +\n 'font-size:11.5px;' +\n 'line-height:1.55;' +\n 'background:' +\n (\n laat\n ? 'rgba(224,123,0,.08)'\n : '#f6f4fa'\n ) +\n ';border:1px solid ' +\n (\n laat\n ? 'rgba(224,123,0,.35)'\n : D.rand\n ) +\n ';color:' +\n (\n laat\n ? '#5a4a33'\n : D.grijs\n ) +\n ';',\n\n 'Balans heeft verwerkt tot en met ' +\n laatsteDag.getDate() +\n ' ' +\n MND_LANG[\n laatsteDag.getMonth()\n ] +\n ' \\u2014 ' +\n achter +\n ' dag' +\n (\n achter === 1\n ? ''\n : 'en'\n ) +\n ' achterstand. ' +\n (\n laat\n ? 'Dat is meer dan de gebruikelijke twee dagen.'\n : 'Dat is normaal.'\n )\n );\n\n c.id =\n 'be-verwerkt';\n\n return c;\n }\n\n\n /*\n * ============================================================\n * RESULTATEN — toggle + instellingen\n * ============================================================\n */\n\n function injecteerKop() {\n if (\n document.getElementById(\n 'be-kop'\n )\n ) {\n return;\n }\n\n var k =\n document.querySelector(\n '.results-summary-card'\n );\n\n var hdr =\n k\n ? k.querySelector(\n '.card-header'\n )\n : null;\n\n if (!hdr) {\n return;\n }\n\n var cs =\n getComputedStyle(\n hdr\n );\n\n if (\n cs.display !==\n 'flex'\n ) {\n hdr.style.display =\n 'flex';\n\n hdr.style.alignItems =\n 'center';\n\n hdr.style.flexWrap =\n 'wrap';\n\n hdr.style.gap =\n '12px';\n }\n\n var wrap =\n el(\n 'div',\n 'display:flex;' +\n 'align-items:center;' +\n 'gap:11px;' +\n 'margin-left:auto;' +\n 'user-select:none;'\n );\n\n wrap.id =\n 'be-kop';\n\n var lbl =\n el(\n 'span',\n 'font-size:13px;' +\n 'font-weight:600;' +\n 'letter-spacing:.03em;' +\n 'transition:color .2s;' +\n 'color:' +\n (\n cfg.allin\n ? D.paars\n : D.paarsLicht\n ) +\n ';',\n 'All-in'\n );\n\n var track =\n el(\n 'div',\n 'position:relative;' +\n 'width:44px;' +\n 'height:24px;' +\n 'border-radius:12px;' +\n 'flex-shrink:0;' +\n 'border:1.5px solid ' +\n D.paarsLicht +\n ';transition:background .25s;' +\n 'background:' +\n (\n cfg.allin\n ? D.paars\n : D.rand\n ) +\n ';'\n );\n\n var thumb =\n el(\n 'div',\n 'position:absolute;' +\n 'top:2px;' +\n 'left:2px;' +\n 'width:16px;' +\n 'height:16px;' +\n 'border-radius:50%;' +\n 'background:#fff;' +\n 'box-shadow:0 1px 4px rgba(0,0,0,.25);' +\n 'transition:transform .25s;' +\n 'transform:translateX(' +\n (\n cfg.allin\n ? '20px'\n : '0'\n ) +\n ');'\n );\n\n track.appendChild(\n thumb\n );\n\n var schakel =\n el(\n 'label',\n 'display:flex;' +\n 'align-items:center;' +\n 'gap:9px;' +\n 'cursor:pointer;'\n );\n\n schakel.appendChild(\n lbl\n );\n\n schakel.appendChild(\n track\n );\n\n schakel.addEventListener(\n 'click',\n function (e) {\n e.preventDefault();\n\n cfg.allin =\n !cfg.allin;\n localStorage.setItem(LS.allin, cfg.allin ? 'aan' : 'uit');\n\n lbl.style.color =\n cfg.allin\n ? D.paars\n : D.paarsLicht;\n\n track.style.background =\n cfg.allin\n ? D.paars\n : D.rand;\n\n thumb.style.transform =\n cfg.allin\n ? 'translateX(20px)'\n : 'translateX(0)';\n\n herteken();\n }\n );\n\n wrap.appendChild(\n schakel\n );\n\n var gear =\n el(\n 'div',\n 'width:18px;' +\n 'height:18px;' +\n 'cursor:pointer;' +\n 'opacity:.6;' +\n 'flex-shrink:0;' +\n 'transition:opacity .2s;'\n );\n\n gear.innerHTML =\n '' +\n\n '' +\n\n '' +\n\n '';\n\n gear.addEventListener(\n 'mouseenter',\n function () {\n gear.style.opacity =\n '1';\n }\n );\n\n gear.addEventListener(\n 'mouseleave',\n function () {\n gear.style.opacity =\n '.6';\n }\n );\n\n gear.addEventListener(\n 'click',\n function (e) {\n e.stopPropagation();\n\n var p =\n document.getElementById(\n 'be-paneel'\n );\n\n if (p) {\n p.remove();\n return;\n }\n\n openInstellingen(\n hdr\n );\n }\n );\n\n wrap.appendChild(\n gear\n );\n\n hdr.appendChild(\n wrap\n );\n }\n\n function veld(\n id,\n label,\n waarde,\n type\n ) {\n return (\n '
' +\n\n '' +\n\n '' +\n\n '
'\n );\n }\n\n function knopje(\n id,\n titel,\n uitleg,\n aan\n ) {\n return (\n '
' +\n\n '
' +\n\n '
' +\n titel +\n '
' +\n\n '
' +\n uitleg +\n '
' +\n\n '
' +\n\n '
' +\n\n '
' +\n '
' +\n\n '
' +\n\n '
'\n );\n }\n\n function hangKnopje(\n id,\n staat,\n sleutel\n ) {\n var e =\n document.getElementById(\n id\n );\n\n if (!e) {\n return;\n }\n\n e.addEventListener(\n 'click',\n function () {\n staat[sleutel] =\n !staat[sleutel];\n\n e.style.background =\n staat[sleutel]\n ? D.paars\n : D.rand;\n\n e.firstElementChild\n .style.left =\n staat[sleutel]\n ? '18px'\n : '3px';\n }\n );\n }\n\n function openInstellingen(hdr) {\n var p =\n el(\n 'div',\n 'position:absolute;' +\n 'z-index:9999;' +\n 'background:#fff;' +\n 'border:1px solid ' +\n D.rand +\n ';border-radius:10px;' +\n 'padding:17px 19px;' +\n 'width:330px;' +\n 'box-shadow:0 6px 22px rgba(107,63,160,.16);'\n );\n\n p.id =\n 'be-paneel';\n\n var r =\n hdr.getBoundingClientRect();\n\n p.style.top =\n (\n window.scrollY +\n r.bottom +\n 6\n ) +\n 'px';\n\n p.style.left =\n Math.max(\n 12,\n window.scrollX +\n r.right -\n 340\n ) +\n 'px';\n\n p.innerHTML =\n '
' +\n\n '
' +\n 'Instellingen v4.6.5' +\n '
' +\n\n '' +\n '\\u00d7' +\n '' +\n\n '
' +\n\n veld(\n 'be-p-voorschot',\n 'Voorschot per maand (\\u20ac)',\n cfg.voorschot.toFixed(2),\n 'number'\n ) +\n\n veld(\n 'be-p-start',\n 'Startdatum contract (dd-mm-jjjj)',\n n2(\n cfg.start.getDate()\n ) +\n '-' +\n n2(\n cfg.start.getMonth() + 1\n ) +\n '-' +\n cfg.start.getFullYear(),\n 'text'\n ) +\n\n knopje('be-p-dark', 'Donkere modus', 'Op alle pagina’s, met behoud van de bestaande indeling', beDarkOn) +\n\n knopje(\n 'be-p-sald',\n 'Salderingsbalans tonen',\n 'Import tegen export over het contractjaar',\n cfg.saldering\n ) +\n\n knopje(\n 'be-p-balans',\n 'Blok \"Balans Systeem resultaat\" tonen',\n 'Besparing tegenover een vast contract, exclusief energiebelasting',\n cfg.balansBlok\n ) +\n\n '
' +\n\n '' +\n\n '' +\n\n '
' +\n\n '
' +\n '
';\n\n document.body.appendChild(\n p\n );\n\n document.getElementById(\n 'be-p-sluit'\n ).addEventListener(\n 'click',\n function () {\n p.remove();\n }\n );\n\n var staat = {\n dark: beDarkOn,\n sald:\n cfg.saldering,\n\n balans:\n cfg.balansBlok\n };\n\n hangKnopje('be-p-dark', staat, 'dark');\n\n hangKnopje(\n 'be-p-sald',\n staat,\n 'sald'\n );\n\n hangKnopje(\n 'be-p-balans',\n staat,\n 'balans'\n );\n\n document.getElementById(\n 'be-p-wis'\n ).addEventListener(\n 'click',\n function () {\n var m =\n document.getElementById(\n 'be-p-melding'\n );\n\n if (\n !confirm(\n 'Restanten van eerdere versies uit localStorage wissen? De instellingen blijven staan.'\n )\n ) {\n return;\n }\n\n var n =\n wisOpslag();\n\n m.style.color =\n n > 0\n ? D.oranje\n : D.grijs;\n\n m.textContent =\n n > 0\n ? (\n n +\n ' oude sleutel' +\n (\n n === 1\n ? ''\n : 's'\n ) +\n ' gewist.'\n )\n : (\n 'Niets te wissen \\u2014 er stond niets ouds meer in.'\n );\n\n m.style.display =\n 'block';\n\n herteken();\n }\n );\n\n document.getElementById(\n 'be-p-op'\n ).addEventListener(\n 'click',\n function () {\n var m =\n document.getElementById(\n 'be-p-melding'\n );\n\n function fout(t) {\n m.style.color =\n D.rood;\n\n m.textContent =\n t;\n\n m.style.display =\n 'block';\n }\n\n var v =\n parseFloat(\n document.getElementById(\n 'be-p-voorschot'\n ).value\n );\n\n var s =\n document.getElementById(\n 'be-p-start'\n )\n .value\n .trim()\n .split('-');\n\n if (\n isNaN(v) ||\n v < 0\n ) {\n return fout(\n 'Ongeldig voorschotbedrag.'\n );\n }\n\n if (\n s.length !== 3\n ) {\n return fout(\n 'Datum moet dd-mm-jjjj zijn.'\n );\n }\n\n var d =\n new Date(\n parseInt(\n s[2],\n 10\n ),\n parseInt(\n s[1],\n 10\n ) - 1,\n parseInt(\n s[0],\n 10\n )\n );\n\n if (\n isNaN(\n d.getTime()\n )\n ) {\n return fout(\n 'Ongeldige datum.'\n );\n }\n\n beDarkApply(staat.dark);\n\n cfg.voorschot =\n v;\n\n cfg.start =\n d;\n\n cfg.saldering =\n staat.sald;\n\n cfg.balansBlok =\n staat.balans;\n\n localStorage.setItem(\n LS.voorschot,\n v.toFixed(2)\n );\n\n localStorage.setItem(\n LS.startdatum,\n iso(d)\n );\n\n localStorage.setItem(\n LS.saldering,\n staat.sald\n ? 'aan'\n : 'uit'\n );\n\n localStorage.setItem(\n LS.balansBlok,\n staat.balans\n ? 'aan'\n : 'uit'\n );\n\n contractCache =\n null;\n\n dagenLopend =\n null;\n\n m.style.color =\n D.groen;\n\n m.textContent =\n 'Opgeslagen.';\n\n m.style.display =\n 'block';\n\n herteken();\n }\n );\n }\n\n function wisOpslag() {\n var weg = [];\n\n try {\n Object.keys(\n localStorage\n ).forEach(\n function (k) {\n if (\n k.indexOf('be_') === 0 &&\n k.indexOf(\n 'be_cfg_'\n ) !== 0\n ) {\n weg.push(k);\n }\n }\n );\n\n weg.forEach(\n function (k) {\n localStorage.removeItem(\n k\n );\n }\n );\n\n } catch (e) {}\n\n contractCache =\n null;\n\n dagenLopend =\n null;\n\n cacheJaar = {};\n\n return weg.length;\n }\n\n\n /*\n * ============================================================\n * START\n * ============================================================\n */\n\n function pols() {\n if (\n location.pathname.indexOf(\n '/results'\n ) === -1\n ) {\n return;\n }\n\n injecteerKop();\n\n regelBalansBlok();\n\n resultatenHaalVoorlopigeDagen()\n .then(\n function (ok) {\n if (ok) {\n plan();\n }\n }\n );\n }\n\n function start() {\n new MutationObserver(\n pols\n ).observe(\n document.body,\n {\n childList: true,\n subtree: true\n }\n );\n\n startActueelAllin();\n beArchiefOnderhoud();\n setInterval(beArchiefOnderhoud, 300000);\n\n pols();\n\n if (\n location.pathname.indexOf(\n '/results'\n ) !== -1\n ) {\n setTimeout(\n plan,\n 400\n );\n\n setTimeout(\n plan,\n 1600\n );\n }\n }\n\n if (document.body) {\n start();\n\n } else {\n document.addEventListener(\n 'DOMContentLoaded',\n start\n );\n }\n\n \n\n \n})();\n\n\n/*\n * ============================================================\n * ABSURD UNITS — alleen actief wanneer Resultaten All-in AAN staat\n * Los getest als v0.5 en hier ongewijzigd geïntegreerd.\n * Jaar: originele Balans-jaargrafiek blijft intact.\n * Dag/Maand: Absurd Units mogen ook de samenvattingsgrafiek overnemen.\n * ============================================================\n */\n\n(function () {\n 'use strict';\n\n /* Ook het losse Absurd Units-deel mag maar één DOM-schrijver hebben. */\n var BE_ABSURD_GUARD =\n 'data-be-absurd-runtime';\n\n if (\n document.documentElement.hasAttribute(\n BE_ABSURD_GUARD\n )\n ) {\n return;\n }\n\n document.documentElement.setAttribute(\n BE_ABSURD_GUARD,\n '4.6.5'\n );\n\n var TAG =\n '[BE Absurd Units]';\n\n var laatsteSleutel =\n null;\n\n var sessieSeed =\n null;\n\n var domOrigineel = {};\n var chartOrigineel =\n null;\n\n var huidigeSet =\n null;\n\n /*\n * Bewaar de ECHTE Balans-bronwaarde per periode apart.\n * Daarna mag data-counter-value van het DOM-element weg,\n * zodat de ingebouwde Balans-counter onze absurdwaarde\n * niet steeds terug naar euro's probeert te schrijven.\n */\n var bronBedragen =\n {};\n\n /*\n * Externe grapbron.\n *\n * De bron is de bestaande Wikipedia-lijst \"List of humorous units\n * of measurement\". We bewaren een succesvolle lijst 24 uur lokaal.\n * Als Wikipedia/CORS/CSP niet beschikbaar is, blijft de ingebouwde\n * v4.4.7-verzameling gewoon als fallback werken.\n */\n var ABSURD_WIKI_API =\n 'https://en.wikipedia.org/w/api.php?' +\n 'action=parse&' +\n 'page=List_of_humorous_units_of_measurement&' +\n 'prop=text&' +\n 'format=json&' +\n 'formatversion=2&' +\n 'origin=*';\n\n var ABSURD_CACHE_KEY =\n 'be_absurd_wikipedia_units_v1';\n\n var ABSURD_HISTORY_KEY =\n 'be_absurd_units_history_v1';\n\n var ABSURD_CACHE_MS =\n 24 * 60 * 60 * 1000;\n\n var externeThemas = [];\n var absurdBronVersie = 0;\n var externeThemasBezig = false;\n\n var ABSURD_KORTE_UITLEG = [\n 'Ongeveer exact.',\n 'Meetbaar bij goed weer.',\n 'Gekeurd door niemand.',\n 'Bij benadering officieel.',\n 'Met liefde verkeerd gemeten.',\n 'IJking zoek, resultaat gevonden.',\n 'Nauwkeurig genoeg voor koffie.',\n 'Wetenschappelijk gezien: vooruit.',\n 'Afgerond door een professional.',\n 'Volgens de rekenkabouter klopt het.'\n ];\n\n function absurdKorteUitleg(set) {\n var sleutel =\n String(set.bronId || '') +\n '|' +\n String(set.eenheid || '') +\n '|' +\n formatWaarde(\n set.totaal,\n set.code\n );\n\n return ABSURD_KORTE_UITLEG[\n seedVan(sleutel) %\n ABSURD_KORTE_UITLEG.length\n ];\n }\n\n function getal(v) {\n var n =\n Number(v);\n\n return Number.isFinite(n)\n ? n\n : 0;\n }\n\n function nl(v, dec) {\n return Number(v)\n .toLocaleString(\n 'nl-NL',\n {\n minimumFractionDigits:\n dec,\n maximumFractionDigits:\n dec\n }\n );\n }\n\n function leesBedrag(\n periode\n ) {\n var e =\n document.getElementById(\n 'summary-trade-savings-total'\n );\n\n if (!e) {\n return null;\n }\n\n /*\n * Alleen data-counter-value vertrouwen als verse/native bron.\n * De hoofdsite zet die bij een nieuwe periode opnieuw.\n */\n var attr =\n e.getAttribute(\n 'data-counter-value'\n );\n\n if (\n attr !== null &&\n attr !== ''\n ) {\n var n =\n parseFloat(\n String(attr)\n .replace(\n ',',\n '.'\n )\n );\n\n if (Number.isFinite(n)) {\n bronBedragen[\n periode\n ] =\n n;\n\n return n;\n }\n }\n\n /*\n * Zodra wij het element hebben overgenomen, staat er expres\n * geen data-counter-value meer. Dan gebruiken we uitsluitend\n * de eerder vastgelegde echte Balans-waarde voor deze periode.\n */\n if (\n Object.prototype\n .hasOwnProperty.call(\n bronBedragen,\n periode\n )\n ) {\n return bronBedragen[\n periode\n ];\n }\n\n /*\n * Alleen vóór onze eerste overname is textContent nog veilig.\n */\n if (!huidigeSet) {\n var tekst =\n String(\n e.textContent || ''\n )\n .replace(\n /\\./g,\n ''\n )\n .replace(\n ',',\n '.'\n );\n\n var m =\n tekst.match(\n /-?\\d+(?:\\.\\d+)?/\n );\n\n if (m) {\n var t =\n parseFloat(\n m[0]\n );\n\n if (Number.isFinite(t)) {\n bronBedragen[\n periode\n ] =\n t;\n\n return t;\n }\n }\n }\n\n return null;\n }\n\n function periodeTekst() {\n var e =\n document.querySelector(\n '.trade-savings-period-label'\n );\n\n return e\n ? e.textContent.trim()\n : '';\n }\n\n function jaarActief() {\n var knoppen =\n document.querySelectorAll(\n 'button, a'\n );\n\n for (\n var i = 0;\n i < knoppen.length;\n i++\n ) {\n var b =\n knoppen[i];\n\n if (\n String(\n b.textContent || ''\n ).trim() !==\n 'Jaar'\n ) {\n continue;\n }\n\n if (\n b.classList.contains(\n 'active'\n ) ||\n b.getAttribute(\n 'aria-selected'\n ) ===\n 'true' ||\n b.getAttribute(\n 'aria-pressed'\n ) ===\n 'true'\n ) {\n return true;\n }\n }\n\n return false;\n }\n\n function allinAan() {\n var waarde =\n document.getElementById(\n 'summary-stroom-voordeel'\n );\n\n var kaart =\n waarde\n ? waarde.closest(\n '.summary-compact-metric'\n )\n : null;\n\n var label =\n kaart\n ? kaart.querySelector(\n '.summary-compact-label'\n )\n : null;\n\n return !!(\n label &&\n /all-in/i.test(\n label.textContent || ''\n )\n );\n }\n\n function seedVan(tekst) {\n var h =\n 2166136261;\n\n for (\n var i = 0;\n i < tekst.length;\n i++\n ) {\n h ^=\n tekst.charCodeAt(i);\n\n h =\n Math.imul(\n h,\n 16777619\n );\n }\n\n return h >>> 0;\n }\n\n function rng(seed) {\n var x =\n seed || 123456789;\n\n return function () {\n x +=\n 0x6D2B79F5;\n\n var t =\n x;\n\n t =\n Math.imul(\n t ^\n (\n t >>> 15\n ),\n t | 1\n );\n\n t ^=\n t +\n Math.imul(\n t ^\n (\n t >>> 7\n ),\n t | 61\n );\n\n return (\n (\n t ^\n (\n t >>> 14\n )\n ) >>> 0\n ) /\n 4294967296;\n };\n }\n\n function kies(r, lijst) {\n return lijst[\n Math.floor(\n r() *\n lijst.length\n )\n ];\n }\n\n function haalSessieSeed() {\n if (\n sessieSeed !==\n null\n ) {\n return sessieSeed;\n }\n\n var a =\n new Uint32Array(\n 1\n );\n\n crypto.getRandomValues(\n a\n );\n\n sessieSeed =\n a[0];\n\n return sessieSeed;\n }\n\n function schoonAbsurdTekst(s) {\n return String(\n s || ''\n )\n .replace(\n /\\[[^\\]]*\\]/g,\n ''\n )\n .replace(\n /\\s+/g,\n ' '\n )\n .trim();\n }\n\n function kortAbsurdTekst(\n s,\n max\n ) {\n var t =\n schoonAbsurdTekst(\n s\n );\n\n if (\n t.length <= max\n ) {\n return t;\n }\n\n t =\n t.slice(\n 0,\n max - 1\n );\n\n var sp =\n t.lastIndexOf(\n ' '\n );\n\n if (\n sp >\n max * 0.6\n ) {\n t =\n t.slice(\n 0,\n sp\n );\n }\n\n return (\n t +\n '…'\n );\n }\n\n function absurdCode(\n naam\n ) {\n var woorden =\n String(\n naam || ''\n )\n .replace(\n /\\([^)]*\\)/g,\n ' '\n )\n .replace(\n /[^A-Za-z0-9]+/g,\n ' '\n )\n .trim()\n .split(\n /\\s+/\n )\n .filter(Boolean);\n\n if (!woorden.length) {\n return '???';\n }\n\n if (\n woorden.length === 1\n ) {\n return woorden[0]\n .slice(\n 0,\n 3\n )\n .toUpperCase();\n }\n\n return woorden\n .slice(\n 0,\n 4\n )\n .map(\n function (w) {\n return w.charAt(0);\n }\n )\n .join('')\n .toUpperCase();\n }\n\n function absurdCategorie(\n heading\n ) {\n var n =\n heading;\n\n while (\n n\n ) {\n n =\n n.previousElementSibling;\n\n if (!n) {\n break;\n }\n\n if (\n n.tagName ===\n 'H2'\n ) {\n var h =\n n.querySelector(\n '.mw-headline'\n );\n\n return schoonAbsurdTekst(\n h\n ? h.textContent\n : n.textContent\n );\n }\n }\n\n return (\n 'onclassificeerbare metrologie'\n );\n }\n\n function parseWikipediaThemas(\n html\n ) {\n var doc =\n new DOMParser()\n .parseFromString(\n html,\n 'text/html'\n );\n\n var uit = [];\n var gezien = {};\n\n doc.querySelectorAll(\n '.mw-parser-output h3, ' +\n '.mw-parser-output h4'\n )\n .forEach(\n function (heading) {\n var span =\n heading.querySelector(\n '.mw-headline'\n );\n\n var naam =\n schoonAbsurdTekst(\n span\n ? span.textContent\n : heading.textContent\n );\n\n if (\n !naam ||\n naam.length > 90\n ) {\n return;\n }\n\n var laag =\n naam.toLowerCase();\n\n if (\n gezien[laag] ||\n /^(see also|notes|references|bibliography|external links)$/i.test(\n naam\n )\n ) {\n return;\n }\n\n var n =\n heading.nextElementSibling;\n\n var beschrijving =\n '';\n\n while (n) {\n if (\n /^H[234]$/.test(\n n.tagName\n )\n ) {\n break;\n }\n\n if (\n n.tagName ===\n 'P'\n ) {\n var p =\n schoonAbsurdTekst(\n n.textContent\n );\n\n if (\n p.length >= 28\n ) {\n beschrijving =\n p;\n\n break;\n }\n }\n\n n =\n n.nextElementSibling;\n }\n\n if (\n !beschrijving\n ) {\n return;\n }\n\n gezien[laag] =\n true;\n\n uit.push({\n id:\n 'wiki:' +\n laag,\n\n naam:\n naam,\n\n categorie:\n absurdCategorie(\n heading\n ),\n\n beschrijving:\n kortAbsurdTekst(\n beschrijving,\n 190\n )\n });\n }\n );\n\n return uit;\n }\n\n function leesAbsurdCache() {\n try {\n var c =\n JSON.parse(\n localStorage.getItem(\n ABSURD_CACHE_KEY\n ) ||\n 'null'\n );\n\n if (\n !c ||\n !Array.isArray(\n c.items\n ) ||\n c.items.length < 5\n ) {\n return null;\n }\n\n return c;\n\n } catch (e) {\n return null;\n }\n }\n\n function schrijfAbsurdCache(\n items\n ) {\n try {\n localStorage.setItem(\n ABSURD_CACHE_KEY,\n JSON.stringify({\n tijd:\n Date.now(),\n\n items:\n items\n })\n );\n\n } catch (e) {}\n }\n\n function leesAbsurdHistorie() {\n try {\n var h =\n JSON.parse(\n localStorage.getItem(\n ABSURD_HISTORY_KEY\n ) ||\n '[]'\n );\n\n return Array.isArray(h)\n ? h\n : [];\n\n } catch (e) {\n return [];\n }\n }\n\n function onthoudAbsurdGebruik(\n id\n ) {\n if (!id) {\n return;\n }\n\n var h =\n leesAbsurdHistorie()\n .filter(\n function (x) {\n return (\n x !== id\n );\n }\n );\n\n h.unshift(\n id\n );\n\n h =\n h.slice(\n 0,\n 24\n );\n\n try {\n localStorage.setItem(\n ABSURD_HISTORY_KEY,\n JSON.stringify(\n h\n )\n );\n\n } catch (e) {}\n }\n\n function kiesAbsurdThema(\n r,\n lijst\n ) {\n var historie =\n leesAbsurdHistorie();\n\n var vers =\n lijst.filter(\n function (t) {\n return (\n historie.indexOf(\n t.id\n ) === -1\n );\n }\n );\n\n /*\n * Pas nadat de recente 24 vrijwel alles uitsluiten, mag de zak\n * opnieuw beginnen. Daardoor zie je veel minder snel dezelfde unit.\n */\n var bron =\n vers.length\n ? vers\n : lijst;\n\n return kies(\n r,\n bron\n );\n }\n\n function wikiItemNaarThema(\n item\n ) {\n var h =\n seedVan(\n item.id\n );\n\n var r =\n rng(\n h\n );\n\n var factor =\n 0.35 +\n r() *\n 18;\n\n var correcties1 = [\n 'Historische conversiecorrectie',\n 'Onnodige kalibratiemarge',\n 'Referentie-afwijking',\n 'Conventionele onzekerheid',\n 'Metrologische zijstap',\n 'Traditionele afrondingsmarge'\n ];\n\n var correcties2 = [\n 'Wikipedia-meetmarge',\n 'Documentatiecorrectie',\n 'Bronvermeldingsopslag',\n 'Ceremoniële restwaarde',\n 'Niet-SI afrondingscorrectie',\n 'Onverklaarde systeemtoeslag'\n ];\n\n return {\n id:\n item.id,\n\n eenheid:\n item.naam,\n\n code:\n absurdCode(\n item.naam\n ),\n\n factor:\n factor,\n\n kop:\n 'Resultaat uitgedrukt in ' +\n item.naam,\n\n toelichting:\n item.beschrijving,\n\n vergelijking:\n 'Bestaande humor-eenheid uit Wikipedia · ' +\n item.categorie,\n\n delen: [\n 'Nominale ' +\n item.naam,\n\n kies(\n r,\n correcties1\n ),\n\n kies(\n r,\n correcties2\n )\n ]\n };\n }\n\n function haalExterneThemas() {\n if (\n externeThemasBezig\n ) {\n return;\n }\n\n var cache =\n leesAbsurdCache();\n\n if (\n cache &&\n cache.items.length\n ) {\n externeThemas =\n cache.items\n .map(\n wikiItemNaarThema\n );\n\n absurdBronVersie++;\n\n console.log(\n TAG,\n externeThemas.length,\n 'Wikipedia-units uit lokale cache geladen.'\n );\n }\n\n if (\n cache &&\n Number.isFinite(\n cache.tijd\n ) &&\n Date.now() -\n cache.tijd <\n ABSURD_CACHE_MS\n ) {\n return;\n }\n\n externeThemasBezig =\n true;\n\n window.fetch(\n ABSURD_WIKI_API,\n {\n method:\n 'GET',\n\n mode:\n 'cors',\n\n credentials:\n 'omit',\n\n cache:\n 'no-store'\n }\n )\n .then(\n function (r) {\n if (!r.ok) {\n throw new Error(\n 'HTTP ' +\n r.status\n );\n }\n\n return r.json();\n }\n )\n .then(\n function (j) {\n var html =\n j &&\n j.parse &&\n typeof j.parse.text ===\n 'string'\n ? j.parse.text\n : '';\n\n var items =\n parseWikipediaThemas(\n html\n );\n\n if (\n items.length < 5\n ) {\n throw new Error(\n 'te weinig bruikbare units (' +\n items.length +\n ')'\n );\n }\n\n schrijfAbsurdCache(\n items\n );\n\n externeThemas =\n items.map(\n wikiItemNaarThema\n );\n\n absurdBronVersie++;\n\n /*\n * Zelfde periode/bedrag moet na de eerste succesvolle externe\n * laadbeurt één keer opnieuw gekozen kunnen worden.\n */\n laatsteSleutel =\n null;\n\n console.log(\n TAG,\n externeThemas.length,\n 'humor-units live uit Wikipedia geladen.'\n );\n }\n )\n .catch(\n function (e) {\n console.warn(\n TAG,\n 'Wikipedia-bron niet beschikbaar; ingebouwde fallback blijft actief:',\n e.message\n );\n }\n )\n .then(\n function () {\n externeThemasBezig =\n false;\n }\n );\n }\n\n function lokaleAbsurdThemas() {\n return [\n {\n id:\n 'local:BES',\n\n eenheid:\n 'Bald Eagle-spans',\n\n code:\n 'BES',\n\n factor:\n 8.5,\n\n kop:\n 'Resultaat uitgedrukt in gecertificeerde Bald Eagle-spans',\n\n toelichting:\n 'SI-eenheden waren blijkbaar te eenvoudig',\n\n vergelijking:\n 'Volgens het Freedom Measurement Bureau',\n\n delen: [\n 'Nominale vleugelspanwijdte',\n 'Federale verenopslag',\n 'Patriottische afrondingscorrectie'\n ]\n },\n {\n id:\n 'local:IAB',\n\n eenheid:\n 'ISO-afgekeurde bananenlengtes',\n\n code:\n 'IAB',\n\n factor:\n 31,\n\n kop:\n 'Resultaat uitgedrukt in ISO-afgekeurde bananenlengtes',\n\n toelichting:\n 'gemeten bij twijfelachtige rijpheid en kamertemperatuur',\n\n vergelijking:\n 'Conform norm ISO 0.0-NOPE',\n\n delen: [\n 'Referentiebanaan',\n 'Krommingscorrectie',\n 'Rijpheidstoeslag'\n ]\n },\n {\n id:\n 'local:PKP',\n\n eenheid:\n 'parlementaire koffiepauzes',\n\n code:\n 'PKP',\n\n factor:\n 0.42,\n\n kop:\n 'Resultaat uitgedrukt in parlementaire koffiepauzes',\n\n toelichting:\n 'inclusief vergadertijd waarin aantoonbaar niets gebeurde',\n\n vergelijking:\n 'Volgens de Commissie Tijdverlies & Caffeine',\n\n delen: [\n 'Plenaire cafeïne',\n 'Commissievertraging',\n 'Administratief melkschuim'\n ]\n },\n {\n id:\n 'local:FYD',\n\n eenheid:\n 'vrijheidsyards',\n\n code:\n 'FYD',\n\n factor:\n 4.2,\n\n kop:\n 'Resultaat omgerekend naar volstrekt noodzakelijke vrijheidsyards',\n\n toelichting:\n 'want meters zouden ongepast efficiënt zijn',\n\n vergelijking:\n 'Imperiaal, maar met extra zelfvertrouwen',\n\n delen: [\n 'Basale yardage',\n 'Strategische duimcorrectie',\n 'Freedom overhead'\n ]\n },\n {\n id:\n 'local:BPE',\n\n eenheid:\n 'Brusselse paperclip-equivalenten',\n\n code:\n 'BPE',\n\n factor:\n 93,\n\n kop:\n 'Resultaat in Brusselse paperclip-equivalenten',\n\n toelichting:\n 'goedgekeurd na 14 formulieren en één onleesbare bijlage',\n\n vergelijking:\n 'EU-verordening 404: maatstaf niet gevonden',\n\n delen: [\n 'Directe paperclips',\n 'Gedelegeerde paperclips',\n 'Regelgevende nietjes'\n ]\n },\n {\n id:\n 'local:VVD',\n\n eenheid:\n 'voetbalveld-diagonalen',\n\n code:\n 'VVD',\n\n factor:\n 0.0037,\n\n kop:\n 'Resultaat uitgedrukt in voetbalveld-diagonalen',\n\n toelichting:\n 'de officiële media-eenheid voor alles wat groter is dan een koelkast',\n\n vergelijking:\n 'Omdat vierkante meters kennelijk verboden zijn',\n\n delen: [\n 'Reguliere diagonalen',\n 'Blessuretijdcorrectie',\n 'VAR-meetfout'\n ]\n },\n {\n id:\n 'local:LTL',\n\n eenheid:\n 'luchtvaartwaardige theelepels',\n\n code:\n 'LTL',\n\n factor:\n 57,\n\n kop:\n 'Resultaat in luchtvaartwaardige theelepels',\n\n toelichting:\n 'drukgecompenseerd en volledig ongeschikt voor koffie',\n\n vergelijking:\n 'Aerospace-grade keukenmetrologie',\n\n delen: [\n 'Nominale lepels',\n 'Cabinedrukcorrectie',\n 'Turbulentietoeslag'\n ]\n },\n {\n id:\n 'local:NSM',\n\n eenheid:\n 'notariële schoenmaten',\n\n code:\n 'NSM',\n\n factor:\n 1.8,\n\n kop:\n 'Resultaat uitgedrukt in notarieel vastgelegde schoenmaten',\n\n toelichting:\n 'juridisch bindend zolang niemand daadwerkelijk gaat meten',\n\n vergelijking:\n 'Gewaarmerkt in drievoud',\n\n delen: [\n 'Linkerschoenbasis',\n 'Rechterschoencorrectie',\n 'Aktekosten in natura'\n ]\n },\n {\n id:\n 'local:KIBISH',\n\n eenheid:\n 'imperiale kilobytes',\n\n code:\n 'KiB-ish',\n\n factor:\n 12.7,\n\n kop:\n 'Resultaat geconverteerd naar imperiale kilobytes',\n\n toelichting:\n '1 kilobyte = 12 ounces data, behalve op donderdag',\n\n vergelijking:\n 'Digitale meetkunde uit een parallel universum',\n\n delen: [\n 'Hoofdgeheugen',\n 'Pond-per-bitcorrectie',\n 'Legacy overhead'\n ]\n },\n {\n id:\n 'local:MWL',\n\n eenheid:\n 'ministeriële worstlengtes',\n\n code:\n 'MWL',\n\n factor:\n 6.6,\n\n kop:\n 'Resultaat in ministerieel afgeronde worstlengtes',\n\n toelichting:\n 'de uiteinden zijn administratief buiten beschouwing gelaten',\n\n vergelijking:\n 'Officieel nauwkeurig tot op drie sauzen',\n\n delen: [\n 'Netto worst',\n 'Mosterdcorrectie',\n 'Bestuurlijke darmmarge'\n ]\n }\n ];\n }\n\n function maakThema(\n bedrag,\n periode\n ) {\n var r =\n rng(\n seedVan(\n periode +\n '|' +\n bedrag +\n '|' +\n haalSessieSeed() +\n '|' +\n absurdBronVersie\n )\n );\n\n var themas =\n externeThemas.length >= 5\n ? externeThemas\n : lokaleAbsurdThemas();\n\n var t =\n kiesAbsurdThema(\n r,\n themas\n );\n\n onthoudAbsurdGebruik(\n t.id\n );\n\n var factorVariatie =\n 0.82 +\n r() *\n 0.36;\n\n var totaal =\n bedrag *\n t.factor *\n factorVariatie;\n\n var p1 =\n 0.55 +\n r() *\n 0.16;\n\n var p2 =\n 0.17 +\n r() *\n 0.11;\n\n var p3 =\n 1 -\n p1 -\n p2;\n\n var delen = [\n totaal * p1,\n totaal * p2,\n totaal * p3\n ];\n\n return {\n bronId:\n t.id,\n\n eenheid:\n t.eenheid,\n\n code:\n t.code,\n\n kop:\n t.kop,\n\n toelichting:\n t.toelichting,\n\n vergelijking:\n t.vergelijking,\n\n labels:\n t.delen,\n\n totaal:\n totaal,\n\n delen:\n delen\n };\n }\n\n function decimalen(v) {\n var a =\n Math.abs(v);\n\n if (a >= 1000) {\n return 0;\n }\n\n if (a >= 100) {\n return 1;\n }\n\n if (a >= 10) {\n return 2;\n }\n\n return 3;\n }\n\n function formatWaarde(\n v,\n code\n ) {\n var dec =\n decimalen(v);\n\n return (\n (\n v < 0\n ? '−'\n : ''\n ) +\n nl(\n Math.abs(v),\n dec\n ) +\n ' ' +\n code\n );\n }\n\n function onthoudDom(\n id\n ) {\n if (\n Object.prototype\n .hasOwnProperty.call(\n domOrigineel,\n id\n )\n ) {\n return;\n }\n\n var e =\n document.getElementById(\n id\n );\n\n if (e) {\n domOrigineel[id] = {\n html:\n e.innerHTML,\n\n className:\n e.className,\n\n style:\n e.getAttribute(\n 'style'\n ),\n\n counter:\n e.getAttribute(\n 'data-counter-value'\n )\n };\n }\n }\n\n function onthoudAlles() {\n [\n 'summary-trade-savings-total',\n 'summary-trade-savings-availability',\n 'summary-trade-savings-comparison-copy',\n 'summary-trade-savings-detail',\n 'summary-trade-savings-legend'\n ].forEach(\n onthoudDom\n );\n }\n\n function herstelDom() {\n Object.keys(\n domOrigineel\n ).forEach(\n function (id) {\n var e =\n document.getElementById(\n id\n );\n\n var o =\n domOrigineel[id];\n\n if (!e) {\n return;\n }\n\n e.innerHTML =\n o.html;\n\n e.className =\n o.className;\n\n if (\n o.style === null\n ) {\n e.removeAttribute(\n 'style'\n );\n\n } else {\n e.setAttribute(\n 'style',\n o.style\n );\n }\n\n if (\n o.counter === null\n ) {\n e.removeAttribute(\n 'data-counter-value'\n );\n\n } else {\n e.setAttribute(\n 'data-counter-value',\n o.counter\n );\n }\n }\n );\n }\n\n function chart() {\n return (\n window.Chart &&\n Chart.getChart\n )\n ? Chart.getChart(\n 'summary-trade-savings-chart'\n )\n : null;\n }\n\n function onthoudChart(c) {\n if (\n chartOrigineel ||\n !c\n ) {\n return;\n }\n\n chartOrigineel = {\n labels:\n Array.isArray(\n c.data.labels\n )\n ? c.data.labels.slice()\n : [],\n\n datasets:\n c.data.datasets.map(\n function (d) {\n return {\n label:\n d.label,\n\n data:\n Array.isArray(\n d.data\n )\n ? d.data.slice()\n : [],\n\n hidden:\n !!d.hidden\n };\n }\n ),\n\n tickCallback:\n c.options &&\n c.options.scales &&\n c.options.scales.y &&\n c.options.scales.y.ticks\n ? c.options.scales.y.ticks.callback\n : undefined,\n\n tooltipCallbacks:\n c.options &&\n c.options.plugins &&\n c.options.plugins.tooltip\n ? c.options.plugins.tooltip.callbacks\n : undefined\n };\n }\n\n function herstelChart() {\n var c =\n chart();\n\n if (\n !c ||\n !chartOrigineel\n ) {\n return;\n }\n\n c.data.labels =\n chartOrigineel.labels.slice();\n\n chartOrigineel.datasets\n .forEach(\n function (o, i) {\n var d =\n c.data.datasets[i];\n\n if (!d) {\n return;\n }\n\n d.label =\n o.label;\n\n d.data =\n o.data.slice();\n\n d.hidden =\n o.hidden;\n }\n );\n\n if (\n c.options &&\n c.options.scales &&\n c.options.scales.y &&\n c.options.scales.y.ticks\n ) {\n c.options.scales.y.ticks.callback =\n chartOrigineel.tickCallback;\n }\n\n if (\n c.options &&\n c.options.plugins &&\n c.options.plugins.tooltip\n ) {\n c.options.plugins.tooltip.callbacks =\n chartOrigineel.tooltipCallbacks;\n }\n\n c.update(\n 'none'\n );\n }\n\n function detailRij(\n label,\n waarde,\n index\n ) {\n var kleuren = [\n '#4d216d',\n '#7f3fad',\n '#c28bd3'\n ];\n\n return (\n '
' +\n\n '
' +\n\n '' +\n '' +\n\n '' +\n label +\n '' +\n\n '
' +\n\n '
' +\n waarde +\n '
' +\n\n '
'\n );\n }\n\n function absurdOnderregel(bron) {\n var h = 2166136261;\n var s = String(bron || '');\n for (var i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i);\n h = Math.imul(h, 16777619);\n }\n var x = (h >>> 0) + 0x6D2B79F5;\n var t = Math.imul(x ^ x >>> 15, x | 1);\n t ^= t + Math.imul(t ^ t >>> 7, t | 61);\n var r = ((t ^ t >>> 14) >>> 0) / 4294967296;\n var regels = [\n \"ISO-afgekeurd wegens buitensporige meetbaarheid\",\n \"RDW-geijkt met een bitterbal als referentie\",\n \"EU-conform zolang niemand het narekent\",\n \"Notarieel gewaarmerkt bij normale koffiedruk\",\n \"NEN-LOL 404 · meten is weten-ish\",\n \"Goedgekeurd door het Ministerie van Ongeveer\",\n \"Gekalibreerd met de officiële HEMA-worst\",\n \"NATO-getest op drie geiten en een bureaustoel\",\n \"Wettig meetbaar op oneven dinsdagen\",\n \"CE-gemarkeerd door iemand met een printer\",\n \"Administratief exact tot op een frikandel\",\n \"Geijkt bij volle maan en 2 bar koffiedruk\",\n \"Volgens Brussel voldoende ongeveer\",\n \"Wetenschappelijk plausibel na twee borrels\",\n \"Officieel afgerond tot de dichtstbijzijnde banaan\",\n \"Gecertificeerd door het Bureau Nattevingerwerk\",\n \"DIN-conform mits horizontaal gefrituurd\",\n \"Kalibratie geldig zolang de stagiair meekijkt\",\n \"Goedgekeurd met 95% bestuurlijk vertrouwen\",\n \"SI-onverenigbaar maar bestuurlijk wenselijk\",\n \"Gemeten met een liniaal uit de Action\",\n \"Herleidbaar tot één officieel erkende tuinkabouter\",\n \"Conform protocol: niet schudden vóór gebruik\",\n \"Statistisch verantwoord volgens ome Henk\",\n \"Geijkt tegen de nationale reservebitterbal\",\n \"Formeel exact binnen een foutmarge van ±ja\",\n \"NEN-gecertificeerd na drie bezwaarschriften\",\n \"Internationaal erkend door niemand in het bijzonder\",\n \"Metrologisch verdacht maar gezellig afgerond\",\n \"Goedgekeurd door de Commissie Onnodige Precisie\"\n];\n return regels[Math.floor(r * regels.length)];\n }\n\n function pasDomAan(set) {\n onthoudAlles();\n\n var totaal =\n document.getElementById(\n 'summary-trade-savings-total'\n );\n\n var beschikbaar =\n document.getElementById(\n 'summary-trade-savings-availability'\n );\n\n var vergelijking =\n document.getElementById(\n 'summary-trade-savings-comparison-copy'\n );\n\n var detail =\n document.getElementById(\n 'summary-trade-savings-detail'\n );\n\n if (totaal) {\n var totaalTekst =\n formatWaarde(\n set.totaal,\n set.code\n );\n\n if (\n totaal.textContent !==\n totaalTekst\n ) {\n totaal.textContent =\n totaalTekst;\n }\n\n /*\n * Belangrijk: de echte waarde staat al in bronBedragen.\n * Dit attribuut moet daarna weg, anders blijft Balans'\n * eigen counter de tekst opnieuw naar euro's zetten.\n */\n totaal.removeAttribute(\n 'data-counter-value'\n );\n }\n\n if (beschikbaar) {\n var uitlegTekst =\n absurdKorteUitleg(\n set\n );\n\n if (\n beschikbaar.textContent !==\n uitlegTekst\n ) {\n beschikbaar.textContent =\n uitlegTekst;\n }\n }\n\n if (vergelijking) {\n var bron = String(set.vergelijking || '');\n var vergelijkingTekst =\n bron.indexOf('Bestaande humor-eenheid uit Wikipedia') === 0\n ? absurdOnderregel(\n bron +\n '|' +\n (\n totaal\n ? totaal.textContent\n : undefined\n )\n )\n : bron;\n\n if (bron.indexOf('Bestaande humor-eenheid uit Wikipedia') === 0) {\n vergelijking.style.whiteSpace = 'nowrap';\n }\n\n if (\n vergelijking.textContent !==\n vergelijkingTekst\n ) {\n vergelijking.textContent =\n vergelijkingTekst;\n }\n }\n\n if (detail) {\n detail.style.display =\n 'grid';\n\n detail.style.gridTemplateColumns =\n '1fr';\n\n detail.style.gap =\n '10px';\n\n var detailHtml =\n detailRij(\n set.labels[0],\n formatWaarde(\n set.delen[0],\n set.code\n ),\n 0\n ) +\n detailRij(\n set.labels[1],\n formatWaarde(\n set.delen[1],\n set.code\n ),\n 1\n ) +\n detailRij(\n set.labels[2],\n formatWaarde(\n set.delen[2],\n set.code\n ),\n 2\n );\n\n if (\n detail.innerHTML !==\n detailHtml\n ) {\n detail.innerHTML =\n detailHtml;\n }\n }\n }\n\n function pasLegendaAan(set) {\n var leg =\n document.getElementById(\n 'summary-trade-savings-legend'\n );\n\n if (!leg) {\n return;\n }\n\n onthoudDom(\n 'summary-trade-savings-legend'\n );\n\n var items =\n leg.querySelectorAll(\n '.trade-savings-legend-item'\n );\n\n items.forEach(\n function (item, i) {\n if (i < 3) {\n item.classList.remove(\n 'd-none'\n );\n\n var tekst =\n item.querySelector(\n '.trade-savings-legend-label'\n ) ||\n item.querySelector(\n 'span:last-child'\n );\n\n if (tekst) {\n tekst.textContent =\n set.labels[i];\n }\n\n } else {\n item.classList.add(\n 'd-none'\n );\n }\n }\n );\n }\n\n function pasChartAan(set) {\n /*\n * Jaar is bewust uitgezonderd.\n *\n * Links mag de onzin blijven staan, maar de Balans-jaargrafiek\n * bevat echte maand-op-maand informatie en blijft daarom exact\n * zoals Balans hem zelf rendert.\n */\n if (jaarActief()) {\n return;\n }\n\n var c =\n chart();\n\n if (!c) {\n return;\n }\n\n onthoudChart(\n c\n );\n\n if (\n !c.data.datasets ||\n c.data.datasets.length <\n 3\n ) {\n return;\n }\n\n c.data.labels = [\n set.eenheid\n ];\n\n for (\n var i = 0;\n i < c.data.datasets.length;\n i++\n ) {\n var d =\n c.data.datasets[i];\n\n if (i < 3) {\n d.label =\n set.labels[i];\n\n d.data = [\n set.delen[i]\n ];\n\n d.hidden =\n false;\n\n } else {\n d.data = [\n 0\n ];\n\n d.hidden =\n true;\n }\n }\n\n if (\n c.options &&\n c.options.scales &&\n c.options.scales.y &&\n c.options.scales.y.ticks\n ) {\n c.options.scales.y.ticks.callback =\n function (value) {\n return (\n nl(\n value,\n Math.abs(value) < 10\n ? 1\n : 0\n ) +\n ' ' +\n set.code\n );\n };\n }\n\n if (\n c.options &&\n c.options.plugins &&\n c.options.plugins.tooltip\n ) {\n c.options.plugins.tooltip.callbacks = {\n label:\n function (ctx) {\n var v =\n getal(\n ctx.raw\n );\n\n return (\n ctx.dataset.label +\n ': ' +\n formatWaarde(\n v,\n set.code\n )\n );\n }\n };\n }\n\n c.update(\n 'none'\n );\n\n pasLegendaAan(\n set\n );\n }\n\n function absurdDomKlopt(set) {\n var totaal =\n document.getElementById(\n 'summary-trade-savings-total'\n );\n\n var beschikbaar =\n document.getElementById(\n 'summary-trade-savings-availability'\n );\n\n var vergelijking =\n document.getElementById(\n 'summary-trade-savings-comparison-copy'\n );\n\n if (\n !totaal ||\n !set\n ) {\n return false;\n }\n\n var bron =\n String(\n set.vergelijking || ''\n );\n\n var vergelijkingTekst =\n bron.indexOf(\n 'Bestaande humor-eenheid uit Wikipedia'\n ) === 0\n ? absurdOnderregel(\n bron +\n '|' +\n formatWaarde(\n set.totaal,\n set.code\n )\n )\n : bron;\n\n return (\n String(\n totaal.textContent || ''\n ).trim() ===\n formatWaarde(\n set.totaal,\n set.code\n ) &&\n !!beschikbaar &&\n String(\n beschikbaar.textContent || ''\n ).trim() ===\n absurdKorteUitleg(\n set\n ) &&\n !!vergelijking &&\n String(\n vergelijking.textContent || ''\n ).trim() ===\n vergelijkingTekst\n );\n }\n\n function absurdChartKlopt(set) {\n var c =\n chart();\n\n if (\n !c ||\n !set ||\n !c.data ||\n !Array.isArray(c.data.labels) ||\n c.data.labels.length !== 1 ||\n c.data.labels[0] !== set.eenheid ||\n !Array.isArray(c.data.datasets) ||\n c.data.datasets.length < 3\n ) {\n return false;\n }\n\n for (\n var i = 0;\n i < 3;\n i++\n ) {\n var d =\n c.data.datasets[i];\n\n if (\n !d ||\n d.label !== set.labels[i] ||\n !Array.isArray(d.data) ||\n d.data.length !== 1 ||\n d.data[0] !== set.delen[i] ||\n d.hidden === true\n ) {\n return false;\n }\n }\n\n return true;\n }\n\n function herstelAlles() {\n herstelDom();\n herstelChart();\n\n huidigeSet =\n null;\n }\n\n function werkBij() {\n var periode =\n periodeTekst();\n\n var bedrag =\n leesBedrag(\n periode\n );\n\n var aan =\n allinAan();\n\n if (\n bedrag === null ||\n !periode\n ) {\n return;\n }\n\n var sleutel =\n String(aan) +\n '|' +\n periode +\n '|' +\n bedrag +\n '|' +\n absurdBronVersie;\n\n if (\n sleutel ===\n laatsteSleutel\n ) {\n /*\n * Balans heeft op het grote resultaatbedrag een eigen counter/\n * animatie. Die kan NA onze eerste wijziging alsnog één keer de\n * originele eurowaarde terugschrijven.\n *\n * De sleutel verandert dan niet, dus vroeger deden we hier niets\n * meer met de DOM. Controleer daarom bij dezelfde sleutel of onze\n * absurdwaarde nog werkelijk zichtbaar is en herstel hem alleen\n * wanneer Balans hem heeft overschreven.\n */\n if (\n aan &&\n huidigeSet &&\n !absurdDomKlopt(\n huidigeSet\n )\n ) {\n pasDomAan(\n huidigeSet\n );\n }\n\n /*\n * Chart.js kan later klaar zijn dan de DOM.\n * Werk de grafiek alleen bij wanneer Balans hem echt heeft\n * vervangen. Een onvoorwaardelijke update liet het vlak knipperen.\n */\n if (\n aan &&\n huidigeSet &&\n !jaarActief() &&\n chart() &&\n !absurdChartKlopt(\n huidigeSet\n )\n ) {\n pasChartAan(\n huidigeSet\n );\n }\n\n return;\n }\n\n laatsteSleutel =\n sleutel;\n\n if (!aan) {\n herstelAlles();\n\n console.log(\n TAG,\n 'All-in uit — originele Balans-weergave actief.'\n );\n\n return;\n }\n\n huidigeSet =\n maakThema(\n bedrag,\n periode\n );\n\n pasDomAan(\n huidigeSet\n );\n\n if (!jaarActief()) {\n pasChartAan(\n huidigeSet\n );\n }\n\n console.log(\n TAG,\n huidigeSet\n );\n }\n\n /*\n * Eerst eventueel de lokale Wikipedia-cache gebruiken en daarna,\n * alleen wanneer nodig, de publieke MediaWiki-bron verversen.\n */\n haalExterneThemas();\n\n setInterval(\n werkBij,\n 700\n );\n\n werkBij();\n})();\n\n\n/* Rustâââgh: stabiele live cijfers op Actueel. */\n(function () {\n 'use strict';\n\n if (window.top !== window.self) return;\n\n var RUSTAAGH_RUNTIME_GUARD = 'data-be-rustaagh-runtime';\n if (document.documentElement.getAttribute(RUSTAAGH_RUNTIME_GUARD) === '4.6.5') return;\n document.documentElement.setAttribute(RUSTAAGH_RUNTIME_GUARD, '4.6.5');\n\n var STYLE_ID = 'be-stabiele-cijfers-stijl';\n var MARKER = 'be-stabiel-getal';\n var TOGGLE_ID = 'be-stabiele-cijfers-toggle';\n var OPSLAG = 'be_stabiele_cijfers_aan_v2';\n var scanGepland = false;\n var ingeschakeld = localStorage.getItem(OPSLAG) !== '0';\n\n function voegStijlToe() {\n if (document.getElementById(STYLE_ID)) return;\n\n var stijl = document.createElement('style');\n stijl.id = STYLE_ID;\n stijl.textContent =\n '.' + MARKER + '{' +\n 'display:inline-block!important;' +\n 'box-sizing:content-box!important;' +\n 'max-width:100%!important;' +\n 'white-space:nowrap!important;' +\n 'font-variant-numeric:tabular-nums lining-nums!important;' +\n 'font-feature-settings:\"tnum\" 1,\"lnum\" 1!important;' +\n '}' +\n '.' + MARKER + ',.' + MARKER + ' *{' +\n 'font-variant-numeric:tabular-nums lining-nums!important;' +\n 'font-feature-settings:\"tnum\" 1,\"lnum\" 1!important;' +\n '}' +\n '.' + MARKER + '[data-be-eenheid=\"w\"]{width:8ch!important;}' +\n '.' + MARKER + '[data-be-eenheid=\"kw\"]{width:7ch!important;}' +\n '.' + MARKER + '[data-be-eenheid=\"pct\"]{width:5ch!important;}' +\n '.' + MARKER + '[data-be-eenheid=\"kwh\"]{width:9ch!important;}' +\n '.' + MARKER + '[data-be-eenheid=\"eur\"]{width:9ch!important;}' +\n '.' + MARKER + '[data-be-uitlijning=\"midden\"]{text-align:center!important;}' +\n '.' + MARKER + '[data-be-uitlijning=\"rechts\"]{text-align:right!important;}' +\n '.' + MARKER + '[data-be-uitlijning=\"links\"]{text-align:left!important;}' +\n '.be-stabiele-cijfers-kop{position:relative!important;}' +\n '#' + TOGGLE_ID + '{' +\n 'appearance:none;-webkit-appearance:none;' +\n 'display:flex;align-items:center;gap:8px;' +\n 'width:auto;height:auto;cursor:pointer;' +\n 'border:0;background:transparent;' +\n 'position:absolute;right:24px;top:50%;z-index:2;' +\n 'transform:translateY(-50%);' +\n 'padding:3px 2px;flex:0 0 auto;' +\n 'white-space:nowrap;color:#342f3a;' +\n 'font-family:inherit;font-size:12px;font-weight:500;line-height:1.2;' +\n 'box-shadow:none;' +\n '}' +\n '#' + TOGGLE_ID + ' [data-be-stabiel-track]{' +\n 'position:relative;display:inline-block;' +\n 'width:34px;height:18px;border-radius:999px;flex-shrink:0;' +\n 'transition:background .18s ease;' +\n '}' +\n '#' + TOGGLE_ID + ' [data-be-stabiel-thumb]{' +\n 'position:absolute;left:2px;top:2px;' +\n 'width:14px;height:14px;border-radius:50%;background:#fff;' +\n 'box-shadow:0 1px 3px rgba(0,0,0,.35);' +\n 'transition:transform .18s ease;' +\n '}' +\n '#' + TOGGLE_ID + ' [data-be-stabiel-status]{' +\n 'min-width:24px;font-size:10px;font-weight:700;line-height:1;' +\n '}' +\n '@media(max-width:700px) and (orientation:portrait){' +\n '#' + TOGGLE_ID + '{right:12px!important;gap:6px!important;font-size:11px!important;}' +\n '}';\n\n (document.head || document.documentElement).appendChild(stijl);\n }\n\n function tekst(el) {\n return String(el && el.textContent || '')\n .replace(/\\u00a0/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n }\n\n function eenheidVan(waarde) {\n if (/^[+\\-−]?\\s*€\\s*\\d[\\d\\s.,]*$/i.test(waarde)) return 'eur';\n if (/^[+\\-−]?\\s*\\d[\\d\\s.,]*\\s*kWh$/i.test(waarde)) return 'kwh';\n if (/^[+\\-−]?\\s*\\d[\\d\\s.,]*\\s*kW$/i.test(waarde)) return 'kw';\n if (/^[+\\-−]?\\s*\\d[\\d\\s.,]*\\s*W$/i.test(waarde)) return 'w';\n if (/^[+\\-−]?\\s*\\d[\\d\\s.,]*\\s*%$/i.test(waarde)) return 'pct';\n return null;\n }\n\n function vindTitelElement(titel) {\n var alles = document.querySelectorAll('h1,h2,h3,h4,h5,h6,div,span');\n\n for (var i = 0; i < alles.length; i++) {\n if (tekst(alles[i]) === titel) return alles[i];\n }\n\n return null;\n }\n\n function aantalMeetwaarden(wortel) {\n if (!wortel) return 0;\n\n var aantal = 0;\n var alles = wortel.querySelectorAll('*');\n\n for (var i = 0; i < alles.length; i++) {\n var waarde = tekst(alles[i]);\n if (!eenheidVan(waarde)) continue;\n\n /* Tel één DOM-laag per getal, ook wanneer React dezelfde tekst nestelt. */\n var dubbelKind = false;\n for (var j = 0; j < alles[i].children.length; j++) {\n if (tekst(alles[i].children[j]) === waarde) {\n dubbelKind = true;\n break;\n }\n }\n if (!dubbelKind) aantal++;\n }\n\n return aantal;\n }\n\n function vindPaneel(titel) {\n var titelElement = vindTitelElement(titel);\n if (!titelElement) return null;\n\n var minimaal = titel === 'Laatste meting' ? 4 : 3;\n var paneel = titelElement;\n\n while (paneel.parentElement && paneel !== document.body) {\n paneel = paneel.parentElement;\n\n /*\n * Zoek de eerste voorouder die titel én meetwaarden bevat. Dit gebruikt\n * de DOM-inhoud in plaats van browserafhankelijke pixelafmetingen.\n */\n if (aantalMeetwaarden(paneel) >= minimaal) return paneel;\n }\n\n return null;\n }\n\n function vindKopbalk(titel) {\n var bestaandeKop = document.querySelector('.be-stabiele-cijfers-kop');\n if (bestaandeKop) return bestaandeKop;\n\n var titelElement = vindTitelElement(titel);\n var paneel = vindPaneel(titel);\n if (!titelElement || !paneel) return null;\n\n var kop = titelElement;\n\n while (kop.parentElement && kop.parentElement !== paneel) {\n if (aantalMeetwaarden(kop.parentElement) > 0) break;\n kop = kop.parentElement;\n }\n\n return kop;\n }\n\n function actueelPaneelNummer(el, panelen) {\n var rect = el.getBoundingClientRect();\n if (!rect.width || !rect.height || rect.top < 0) return -1;\n\n for (var i = 0; i < panelen.length; i++) {\n if (panelen[i] && panelen[i].contains(el)) return i;\n }\n\n return -1;\n }\n\n function buitensteZelfdeWaarde(el, waarde) {\n var huidig = el;\n\n while (huidig.parentElement) {\n var ouder = huidig.parentElement;\n var r = ouder.getBoundingClientRect();\n\n if (tekst(ouder) !== waarde || r.width > 220 || r.height > 90) break;\n huidig = ouder;\n }\n\n return huidig;\n }\n\n function verwijderMarkeringen() {\n var gemarkeerd = document.querySelectorAll('.' + MARKER);\n\n for (var i = 0; i < gemarkeerd.length; i++) {\n gemarkeerd[i].classList.remove(MARKER);\n gemarkeerd[i].removeAttribute('data-be-eenheid');\n gemarkeerd[i].removeAttribute('data-be-uitlijning');\n }\n }\n\n function tekenSchakelaar(knop) {\n if (!knop) return;\n\n var track = knop.querySelector('[data-be-stabiel-track]');\n var duim = knop.querySelector('[data-be-stabiel-thumb]');\n var status = knop.querySelector('[data-be-stabiel-status]');\n\n knop.setAttribute('aria-pressed', ingeschakeld ? 'true' : 'false');\n knop.title = ingeschakeld\n ? 'Vaste cijferbreedtes uitschakelen'\n : 'Vaste cijferbreedtes inschakelen';\n track.style.background = ingeschakeld ? '#7040a3' : '#aab4c2';\n duim.style.transform = ingeschakeld ? 'translateX(16px)' : 'translateX(0)';\n var statusTekst = ingeschakeld ? 'AAN' : 'UIT';\n if (status.textContent !== statusTekst) status.textContent = statusTekst;\n }\n\n function voegSchakelaarToe() {\n var kop = vindKopbalk('Live Status');\n if (!kop) return;\n\n kop.classList.add('be-stabiele-cijfers-kop');\n\n var knop = document.getElementById(TOGGLE_ID);\n\n if (!knop) {\n knop = document.createElement('button');\n knop.id = TOGGLE_ID;\n knop.type = 'button';\n knop.innerHTML =\n 'Rustâââgh' +\n '' +\n '' +\n '' +\n '';\n\n knop.addEventListener('click', function () {\n ingeschakeld = !ingeschakeld;\n localStorage.setItem(OPSLAG, ingeschakeld ? '1' : '0');\n tekenSchakelaar(knop);\n\n if (ingeschakeld) {\n planScan();\n } else {\n verwijderMarkeringen();\n }\n });\n }\n\n if (knop.parentElement !== kop) {\n kop.appendChild(knop);\n }\n\n tekenSchakelaar(knop);\n }\n\n function markeerActueleCijfers() {\n scanGepland = false;\n voegStijlToe();\n voegSchakelaarToe();\n\n if (!ingeschakeld) {\n verwijderMarkeringen();\n return;\n }\n\n var elementen = document.body ? document.body.querySelectorAll('*') : [];\n var panelen = [vindPaneel('Live Status'), vindPaneel('Laatste meting')];\n\n for (var i = 0; i < elementen.length; i++) {\n var el = elementen[i];\n var waarde = tekst(el);\n var eenheid = eenheidVan(waarde);\n var paneelNummer = actueelPaneelNummer(el, panelen);\n\n if (!eenheid || paneelNummer < 0) continue;\n\n /*\n * Het SOC-bolletje ligt ín de batterijtekening. Een geforceerde\n * breedte op dat element vervormt daarom het volledige pictogram.\n * De losse SOC-regel rechts krijgt wel gewoon een vaste breedte.\n */\n if (paneelNummer === 0 && eenheid === 'pct') continue;\n\n var doel = buitensteZelfdeWaarde(el, waarde);\n doel.classList.add(MARKER);\n doel.setAttribute('data-be-eenheid', eenheid);\n doel.setAttribute(\n 'data-be-uitlijning',\n paneelNummer === 0 ? 'midden' : 'rechts'\n );\n }\n }\n\n function planScan() {\n if (scanGepland) return;\n scanGepland = true;\n requestAnimationFrame(markeerActueleCijfers);\n }\n\n voegStijlToe();\n planScan();\n\n new MutationObserver(planScan).observe(document.documentElement, {\n childList: true,\n subtree: true,\n characterData: true\n });\n\n window.addEventListener('resize', planScan, { passive: true });\n window.addEventListener('popstate', planScan, { passive: true });\n}());";document.documentElement.appendChild(n);n.remove();}; if(document.documentElement)launch();else document.addEventListener('DOMContentLoaded',launch,{once:true}); })(); (function () { 'use strict'; const ID='be-epex-addon', KEY='be_epex_enabled_v1', CACHE='be_epex_prices_v1'; if(document.getElementById(ID))return; const stateNode=document.createElement('script');stateNode.type='application/json';stateNode.id=ID; document.documentElement.appendChild(stateNode); const read=(key)=>{try{return localStorage.getItem(key);}catch(_){return null;}}; const save=(key,value)=>{try{localStorage.setItem(key,value);}catch(_){}}; let enabled=read(KEY)==='aan', rows=[],cache=null,busy=false,retryAt=0,error='',button,status; try{cache=JSON.parse(read(CACHE));if(cache)rows=normalise(cache.body);}catch(_){cache=null;} function normalise(body){ if(!body||!/^EUR\s*\/\s*MWh$/i.test(body.unit)||!Array.isArray(body.unix_seconds)||!Array.isArray(body.price)||body.unix_seconds.length!==body.price.length)throw Error('Onbekend prijsformaat'); const all=body.unix_seconds.map((t,i)=>({t:t*1000,v:body.price[i]})).filter(r=>Number.isFinite(r.t)).sort((a,b)=>a.t-b.t); const steps=all.slice(1).map((r,i)=>r.t-all[i].t).filter(d=>d>0&&d<=3600000); const step=steps.length?Math.min(...steps):900000; return all.filter(r=>Number.isFinite(r.v)).map(r=>{ const i=all.indexOf(r),next=all[i+1]; return {start:r.t,end:Math.min(r.t+step,next?next.t:r.t+step),price:r.v/1000}; }); } function settings(){ try{return JSON.parse(document.getElementById('be-actueel-allin-data')?.textContent||'{}');}catch(_){return {};} } function publish(){ const s=settings(); stateNode.textContent=JSON.stringify({enabled,rows,allin:s.aan!==false,btw:Number.isFinite(s.btw)?s.btw:1.21,opslag:Number.isFinite(s.opslag)?s.opslag:.02,eb:Number.isFinite(s.eb)?s.eb:.11085}); document.dispatchEvent(new Event('be-epex-data')); if(button){ button.setAttribute('aria-checked',String(enabled)); button.querySelector('.be-epex-state').textContent=enabled?'AAN':'UIT'; button.title=error||'Nederlandse EPEX day-ahead-prijs tonen of verbergen'; } if(status){ status.textContent=enabled&&error?'!':enabled&&busy&&!rows.length?'…':''; status.title=error||'Prijzen ophalen';status.hidden=!status.textContent; } } function range(){ const d=new Date();d.setUTCHours(0,0,0,0);const start=new Date(d.getTime()-2*86400000).toISOString().slice(0,10),end=new Date(d.getTime()+86400000).toISOString().slice(0,10); return {start,end,key:start+'/'+end}; } function request(url){ return new Promise((resolve,reject)=>{ const fn=typeof GM_xmlhttpRequest==='function'?GM_xmlhttpRequest:typeof GM!=='undefined'&&typeof GM.xmlHttpRequest==='function'?GM.xmlHttpRequest.bind(GM):null; if(!fn){reject(Error('De userscriptmanager heeft netwerktoegang nodig'));return;} fn({method:'GET',url,anonymous:true,timeout:20000,onload:r=>{ if(r.status!==200){reject(Error('Prijsbron tijdelijk niet beschikbaar ('+r.status+')'));return;} try{resolve(JSON.parse(r.responseText));}catch(_){reject(Error('Ongeldige prijsresponse'));} },onerror:()=>reject(Error('Prijsbron niet bereikbaar')),ontimeout:()=>reject(Error('Prijsbron reageert niet'))}); }); } async function refresh(){ if(!enabled||busy||document.hidden||!document.getElementById('be-tarief-chart')||Date.now(){enabled=!enabled;save(KEY,enabled?'aan':'uit');publish();refresh();}); status=document.createElement('span');status.id='be-epex-status';status.setAttribute('role','status'); const source=document.createElement('a');source.id='be-epex-source';source.textContent='i'; source.href='https://www.energy-charts.info/api.html';source.target='_blank';source.rel='noopener noreferrer'; source.setAttribute('aria-label','Bron en toelichting EPEX'); source.title='EPEX NL day-ahead. All-in: kale prijs × btw + € 0,02 opslag + energiebelasting. Opslag is inclusief btw en telt één keer mee. Bron: Energy-Charts / Bundesnetzagentur / SMARD · CC BY 4.0.'; const legend=document.createElement('div');legend.id='be-epex-legend';legend.setAttribute('aria-label','Grafieklegenda'); box.append(legend,button,status,source);host.prepend(box);publish();refresh(); } // The main plugin also bridges into the page, where its Chart instance lives. function chartBridge(){ if(window.__beEpexBridge)return;window.__beEpexBridge=true; let data={enabled:false,rows:[]},registered=false; function config(){try{data=JSON.parse(document.getElementById('be-epex-addon').textContent);}catch(_){}} function points(c){ const x=c.options.scales?.x||{},min=x.min??-Infinity,max=x.max??Infinity,out=[]; let previous=null; for(const r of data.rows||[]){ if(r.endmax)continue; if(previous!==null&&r.start>previous)out.push({x:previous,y:null}); const y=data.allin?r.price*data.btw+data.opslag+data.eb:r.price; out.push({x:r.start,y},{x:r.end,y});previous=r.end; } return out; } function install(){ const C=typeof Chart!=='undefined'?Chart:window.Chart;if(!C)return; if(!registered){C.register({id:'beEpexOverlay',beforeUpdate(c){ if(c.canvas.id!=='be-tarief-chart')return; // Write raw configuration, never assign Chart.js option resolvers to themselves. const opts=c.config.options; opts.plugins=opts.plugins||{}; opts.plugins.legend=opts.plugins.legend||{}; opts.plugins.legend.display=false; let ds=c.data.datasets.find(d=>d.beEpex); if(!data.enabled){c.data.datasets=c.data.datasets.filter(d=>!d.beEpex);return;} if(!ds){ds={beEpex:true,label:'EPEX NL',borderColor:'#429ee8',backgroundColor:'#429ee8',borderWidth:2,pointRadius:0,pointHitRadius:8,fill:false,spanGaps:false,stepped:'before',tension:0,order:-10};c.data.datasets.push(ds);} ds.label=data.allin?'EPEX · all-in':'EPEX · kaal';ds.data=points(c); ds.tooltip={callbacks:{label:z=>ds.label+': € '+z.parsed.y.toLocaleString('nl-NL',{minimumFractionDigits:3,maximumFractionDigits:5})+'/kWh'}}; },afterUpdate(c){ if(c.canvas.id!=='be-tarief-chart')return; const host=document.getElementById('be-epex-legend');if(!host)return; const bar=host.parentElement,rect=c.canvas.getBoundingClientRect(),barRect=bar.getBoundingClientRect(); if(c.chartArea&&c.width&&rect.width){ const left=Math.max(0,rect.left+c.chartArea.left*rect.width/c.width-barRect.left); const padding=left.toFixed(2)+'px'; if(bar.style.paddingLeft!==padding)bar.style.paddingLeft=padding; } const items=c.data.datasets.map((d,i)=>({label:d.label,color:d.borderColor,index:i,visible:c.isDatasetVisible(i)})); const key=JSON.stringify(items);if(host.dataset.key===key)return;host.dataset.key=key; host.replaceChildren(); items.forEach(item=>{ const button=document.createElement('button');button.type='button';button.setAttribute('aria-pressed',String(item.visible)); const swatch=document.createElement('span');swatch.className='swatch';swatch.style.backgroundColor=typeof item.color==='string'?item.color:'#888';swatch.setAttribute('aria-hidden','true'); const label=document.createElement('span');label.textContent=item.label;button.append(swatch,label); button.addEventListener('click',()=>{c.setDatasetVisibility(item.index,!c.isDatasetVisible(item.index));c.update('none');});host.append(button); }); }});registered=true;} const c=C.getChart('be-tarief-chart');if(c)c.update('none'); } document.addEventListener('be-epex-data',()=>{config();install();}); document.addEventListener('be-tarief-update',()=>{config();install();}); config();install(); } const bridge=document.createElement('script');bridge.textContent='('+chartBridge.toString()+')();';document.documentElement.appendChild(bridge);bridge.remove(); let pending=false; const observer=new MutationObserver(()=>{if(pending)return;pending=true;setTimeout(()=>{pending=false;mount();},150);}); observer.observe(document.body||document.documentElement,{childList:true,subtree:true}); document.addEventListener('be-actueel-allin-update',publish); document.addEventListener('visibilitychange',()=>{if(!document.hidden){mount();refresh();}}); window.addEventListener('storage',e=>{if(e.key===KEY){enabled=e.newValue==='aan';publish();refresh();}}); mount();setInterval(refresh,60000); })();