PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.7
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.7
2.0.13 2.0.12 2.0.11 2.0.10 2.0.9 trunk 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8
blockenberg / blocks / retirement-calculator / frontend.js

frontend.js in Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor 2.0.7, at blocks/retirement-calculator/frontend.js

251 lines 14.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function () {
2 'use strict';
3
4 function calcRetirement(curAge, retAge, curSavings, monthlyContrib, annualReturn, inflationRate) {
5 var years = Math.max(0, retAge - curAge);
6 var monthlyRate = annualReturn / 100 / 12;
7 var months = years * 12;
8 var futureExisting = curSavings * Math.pow(1 + monthlyRate, months);
9 var futureContribs = monthlyRate > 0
10 ? monthlyContrib * (Math.pow(1 + monthlyRate, months) - 1) / monthlyRate
11 : monthlyContrib * months;
12 var total = futureExisting + futureContribs;
13 var totalContribs = curSavings + monthlyContrib * months;
14 var growth = total - totalContribs;
15 var inflFactor = Math.pow(1 + inflationRate / 100, years);
16 var inflAdjusted = total / inflFactor;
17 return { years: years, total: total, totalContribs: totalContribs, growth: growth, inflAdjusted: inflAdjusted };
18 }
19
20 function fmtM(val, cur, pos) {
21 var n = parseFloat(val);
22 var s;
23 if (n >= 1e6) { s = (n / 1e6).toFixed(2) + 'M'; }
24 else if (n >= 1e3) { s = (n / 1e3).toFixed(1) + 'K'; }
25 else { s = n.toFixed(0); }
26 return pos === 'after' ? s + cur : cur + s;
27 }
28 function fmtFull(val, cur, pos) {
29 var s = Number(parseFloat(val).toFixed(0)).toLocaleString();
30 return pos === 'after' ? s + cur : cur + s;
31 }
32
33 var _typoKeys = {
34 family:'font-family', weight:'font-weight', style:'font-style',
35 decoration:'text-decoration', transform:'text-transform',
36 sizeDesktop:'font-size-d', sizeTablet:'font-size-t', sizeMobile:'font-size-m',
37 lineHeightDesktop:'line-height-d', lineHeightTablet:'line-height-t', lineHeightMobile:'line-height-m',
38 letterSpacingDesktop:'letter-spacing-d', letterSpacingTablet:'letter-spacing-t', letterSpacingMobile:'letter-spacing-m',
39 wordSpacingDesktop:'word-spacing-d', wordSpacingTablet:'word-spacing-t', wordSpacingMobile:'word-spacing-m'
40 };
41 function typoCssVarsForEl(el, obj, prefix) {
42 if (!obj || typeof obj !== 'object') return;
43 Object.keys(_typoKeys).forEach(function(k) {
44 var v = obj[k];
45 if (v === undefined || v === '' || v === null) return;
46 if (k === 'sizeDesktop' || k === 'sizeTablet' || k === 'sizeMobile') v = v + (obj.sizeUnit || 'px');
47 else if (k === 'lineHeightDesktop' || k === 'lineHeightTablet' || k === 'lineHeightMobile') v = v + (obj.lineHeightUnit || '');
48 else if (k === 'letterSpacingDesktop' || k === 'letterSpacingTablet' || k === 'letterSpacingMobile') v = v + (obj.letterSpacingUnit || 'px');
49 else if (k === 'wordSpacingDesktop' || k === 'wordSpacingTablet' || k === 'wordSpacingMobile') v = v + (obj.wordSpacingUnit || 'px');
50 el.style.setProperty(prefix + _typoKeys[k], String(v));
51 });
52 }
53
54 function buildApp(app) {
55 var opts = {};
56 try { opts = JSON.parse(app.getAttribute('data-opts') || '{}'); } catch (e) {}
57
58 var o = opts;
59 var currency = o.currency || '$';
60 var curPos = o.currencyPos || 'before';
61 var radius = (o.cardRadius || 16) + 'px';
62 var inpRad = (o.inputRadius || 8) + 'px';
63 var accentC = o.accentColor || '#6c3fb5';
64 var totalC = o.totalColor || '#6c3fb5';
65 var inflC = o.inflationColor || '#3b82f6';
66 var contribC = o.contribColor || '#10b981';
67 var growthC = o.growthColor || '#f59e0b';
68 var barBg = o.barTrackBg || '#e5e7eb';
69 var labelC = o.labelColor || '#374151';
70 var pjA = o.projBgA || '#f0fdf4';
71 var pjB = o.projBgB || '#eff6ff';
72 var resultBg = o.resultBg || '#f5f3ff';
73 var resultBr = o.resultBorder || '#ede9fe';
74
75 var curAge = parseFloat(o.currentAge) || 30;
76 var retAge = parseFloat(o.retirementAge) || 65;
77 var curSav = parseFloat(o.currentSavings)|| 10000;
78 var contrib = parseFloat(o.monthlyContrib)|| 500;
79 var retRate = parseFloat(o.annualReturn) || 7;
80 var inflRate= parseFloat(o.inflationRate) || 2.5;
81
82 app.innerHTML = '';
83 app.style.fontFamily = 'inherit';
84 app.style.boxSizing = 'border-box';
85
86 typoCssVarsForEl(app, o.titleTypo, '--bkrc-tt-');
87 typoCssVarsForEl(app, o.bodyTypo, '--bkrc-bt-');
88
89 // Section
90 var section = document.createElement('div');
91 section.style.paddingTop = (o.paddingTop || 60) + 'px';
92 section.style.paddingBottom = (o.paddingBottom || 60) + 'px';
93 section.style.background = o.sectionBg || '';
94 app.appendChild(section);
95
96 var card = document.createElement('div');
97 card.style.cssText = 'background:' + (o.cardBg || '#fff') + ';border-radius:' + radius + ';padding:36px 32px;max-width:' + (o.maxWidth || 620) + 'px;margin:0 auto;box-shadow:0 4px 24px rgba(0,0,0,.09);box-sizing:border-box;';
98 section.appendChild(card);
99
100 // Title / subtitle
101 if (o.showTitle || o.showSubtitle) {
102 var hdr = document.createElement('div');
103 hdr.style.marginBottom = '20px';
104 if (o.showTitle) {
105 var ttl = document.createElement('div');
106 ttl.textContent = o.title || 'Retirement Calculator';
107 ttl.className = 'bkbg-ret-title';
108 ttl.style.cssText = 'color:' + (o.titleColor || '#1e1b4b') + ';margin-bottom:6px;';
109 hdr.appendChild(ttl);
110 }
111 if (o.showSubtitle && o.subtitle) {
112 var sub = document.createElement('div');
113 sub.textContent = o.subtitle;
114 sub.className = 'bkbg-ret-subtitle';
115 sub.style.color = o.subtitleColor || '#6b7280';
116 hdr.appendChild(sub);
117 }
118 card.appendChild(hdr);
119 }
120
121 // Input grid
122 var grid = document.createElement('div');
123 grid.className = 'bkbg-ret-input-grid';
124 grid.style.cssText = 'display:grid;grid-template-columns:1fr 1fr;gap:0 20px;margin-bottom:24px;';
125 card.appendChild(grid);
126
127 function makeInput(labelTxt, initVal, min2, max2, step2, suffix, onChange) {
128 var wrap = document.createElement('div');
129 wrap.style.marginBottom = '14px';
130 var lbl = document.createElement('label');
131 lbl.style.cssText = 'display:flex;justify-content:space-between;font-size:13px;font-weight:600;color:' + labelC + ';margin-bottom:5px;';
132 var lblMain = document.createElement('span'); lblMain.textContent = labelTxt;
133 var lblSuf = document.createElement('span'); lblSuf.textContent = suffix || ''; lblSuf.style.cssText = 'font-weight:400;color:#9ca3af;';
134 lbl.appendChild(lblMain); lbl.appendChild(lblSuf);
135 wrap.appendChild(lbl);
136 var inp = document.createElement('input');
137 inp.type = 'number'; inp.value = initVal; inp.min = min2; inp.max = max2; inp.step = step2;
138 inp.style.cssText = 'width:100%;padding:10px 14px;border-radius:' + inpRad + ';border:1.5px solid #e5e7eb;font-size:16px;outline:none;box-sizing:border-box;';
139 inp.addEventListener('focus', function () { inp.style.borderColor = accentC; inp.style.boxShadow = '0 0 0 3px rgba(108,63,181,.15)'; });
140 inp.addEventListener('blur', function () { inp.style.borderColor = '#e5e7eb'; inp.style.boxShadow = ''; });
141 inp.addEventListener('input', function () { onChange(parseFloat(inp.value) || 0); update(); });
142 wrap.appendChild(inp);
143 return wrap;
144 }
145
146 grid.appendChild(makeInput('Current Age', curAge, 18, 80, 1, '', function (v) { curAge = v; }));
147 grid.appendChild(makeInput('Retirement Age', retAge, 40, 90, 1, '', function (v) { retAge = v; }));
148 grid.appendChild(makeInput('Current Savings', curSav, 0, 10000000, 1000, currency, function (v) { curSav = v; }));
149 grid.appendChild(makeInput('Monthly Contribution', contrib, 0, 100000, 50, currency + '/mo', function (v) { contrib = v; }));
150 grid.appendChild(makeInput('Annual Return', retRate, 0, 20, 0.1, '%', function (v) { retRate = v; }));
151 if (o.showInflation) {
152 grid.appendChild(makeInput('Inflation Rate', inflRate, 0, 15, 0.1, '%', function (v) { inflRate = v; }));
153 }
154
155 // Hero result
156 var hero = document.createElement('div');
157 hero.style.cssText = 'background:' + resultBg + ';border:2px solid ' + resultBr + ';border-radius:' + radius + ';padding:28px 28px 22px;margin-bottom:16px;text-align:center;';
158 card.appendChild(hero);
159
160 // Mini cards row
161 var miniRow = document.createElement('div');
162 miniRow.style.cssText = 'display:grid;grid-template-columns:' + (o.showInflation ? '1fr 1fr' : '1fr') + ';gap:12px;margin-bottom:16px;';
163 card.appendChild(miniRow);
164
165 // Breakdown
166 var breakdown = null;
167 if (o.showBreakdown) {
168 breakdown = document.createElement('div');
169 card.appendChild(breakdown);
170 }
171
172 function update() {
173 var r = calcRetirement(curAge, retAge, curSav, contrib, retRate, inflRate);
174 var contribFrac = r.total > 0 ? Math.min(1, r.totalContribs / r.total) : 0;
175 var growthFrac = r.total > 0 ? Math.min(1, r.growth / r.total) : 0;
176 var totalSz = o.totalSize || 52;
177
178 // Hero
179 hero.innerHTML = '';
180 var heroLabel = document.createElement('div');
181 heroLabel.textContent = 'Projected savings at age ' + Math.round(retAge) + ' (' + r.years + ' years)';
182 heroLabel.style.cssText = 'font-size:13px;font-weight:600;color:' + labelC + ';margin-bottom:6px;';
183 hero.appendChild(heroLabel);
184 var heroAmt = document.createElement('div');
185 heroAmt.textContent = fmtM(r.total, currency, curPos);
186 heroAmt.style.cssText = 'font-size:' + totalSz + 'px;font-weight:800;color:' + totalC + ';line-height:1.1;margin-bottom:6px;';
187 hero.appendChild(heroAmt);
188 var heroFull = document.createElement('div');
189 heroFull.textContent = '(' + fmtFull(r.total, currency, curPos) + ' total)';
190 heroFull.style.cssText = 'font-size:13px;color:' + labelC + ';';
191 hero.appendChild(heroFull);
192
193 // Mini cards
194 miniRow.innerHTML = '';
195 if (o.showInflation) {
196 var inflCard = document.createElement('div');
197 inflCard.className = 'bkbg-ret-mini-card';
198 inflCard.style.cssText = 'background:' + pjB + ';border-radius:' + radius + ';padding:16px 18px;';
199 inflCard.innerHTML = '<div style="font-size:11px;font-weight:700;color:' + inflC + ';text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px;">Inflation-Adjusted</div>' +
200 '<div style="font-size:24px;font-weight:800;color:' + inflC + ';">' + fmtM(r.inflAdjusted, currency, curPos) + '</div>' +
201 '<div style="font-size:11px;color:' + labelC + ';margin-top:2px;">In today\'s dollars</div>';
202 miniRow.appendChild(inflCard);
203 }
204 var contribCard = document.createElement('div');
205 contribCard.className = 'bkbg-ret-mini-card';
206 contribCard.style.cssText = 'background:' + pjA + ';border-radius:' + radius + ';padding:16px 18px;';
207 contribCard.innerHTML = '<div style="font-size:11px;font-weight:700;color:' + contribC + ';text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px;">Total Contributions</div>' +
208 '<div style="font-size:24px;font-weight:800;color:' + contribC + ';">' + fmtM(r.totalContribs, currency, curPos) + '</div>' +
209 '<div style="font-size:11px;color:' + labelC + ';margin-top:2px;">What you put in</div>';
210 miniRow.appendChild(contribCard);
211
212 // Breakdown bar
213 if (breakdown) {
214 breakdown.innerHTML = '';
215 var brLabels = document.createElement('div');
216 brLabels.style.cssText = 'display:flex;justify-content:space-between;font-size:12px;color:' + labelC + ';margin-bottom:6px;';
217 brLabels.innerHTML = '<span>Contributions</span><span>Investment Growth</span>';
218 breakdown.appendChild(brLabels);
219
220 var track = document.createElement('div');
221 track.className = 'bkbg-ret-bar-track';
222 track.style.cssText = 'height:14px;border-radius:100px;background:' + barBg + ';overflow:hidden;display:flex;';
223
224 var cBar = document.createElement('div');
225 cBar.className = 'bkbg-ret-bar-contrib';
226 cBar.style.cssText = 'width:0%;height:100%;background:' + contribC + ';border-radius:100px 0 0 100px;transition:width .4s;';
227 var gBar = document.createElement('div');
228 gBar.className = 'bkbg-ret-bar-growth';
229 gBar.style.cssText = 'width:0%;height:100%;background:' + growthC + ';border-radius:0 100px 100px 0;transition:width .4s;';
230
231 track.appendChild(cBar); track.appendChild(gBar);
232 breakdown.appendChild(track);
233 setTimeout(function () {
234 cBar.style.width = (contribFrac * 100).toFixed(1) + '%';
235 gBar.style.width = (growthFrac * 100).toFixed(1) + '%';
236 }, 30);
237
238 var legend = document.createElement('div');
239 legend.style.cssText = 'display:flex;gap:16px;margin-top:8px;font-size:12px;color:' + labelC + ';flex-wrap:wrap;';
240 legend.innerHTML = '<span><span style="display:inline-block;width:10px;height:10px;border-radius:2px;background:' + contribC + ';margin-right:5px;vertical-align:middle;"></span>Contributions: ' + fmtM(r.totalContribs, currency, curPos) + '</span>' +
241 '<span><span style="display:inline-block;width:10px;height:10px;border-radius:2px;background:' + growthC + ';margin-right:5px;vertical-align:middle;"></span>Growth: ' + fmtM(r.growth, currency, curPos) + '</span>';
242 breakdown.appendChild(legend);
243 }
244 }
245
246 update();
247 }
248
249 document.querySelectorAll('.bkbg-ret-app').forEach(buildApp);
250 })();
251