PluginProbe
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor / 2.0.10
Blockenberg — 600+ Advanced Gutenberg Blocks & AI Agent for WordPress Block Editor v2.0.10
2.0.14 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 / compound-interest-calculator / frontend.js

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

278 lines 13.4 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 var _typoKeys = {
5 family:'font-family', weight:'font-weight', style:'font-style',
6 transform:'text-transform', decoration:'text-decoration',
7 sizeDesktop:'font-size-d', sizeTablet:'font-size-t', sizeMobile:'font-size-m',
8 lineHeightDesktop:'line-height-d', lineHeightTablet:'line-height-t', lineHeightMobile:'line-height-m',
9 letterSpacingDesktop:'letter-spacing-d', letterSpacingTablet:'letter-spacing-t', letterSpacingMobile:'letter-spacing-m',
10 wordSpacingDesktop:'word-spacing-d', wordSpacingTablet:'word-spacing-t', wordSpacingMobile:'word-spacing-m'
11 };
12 var _typoUnits = { size:'sizeUnit', lineHeight:'lineHeightUnit', letterSpacing:'letterSpacingUnit', wordSpacing:'wordSpacingUnit' };
13 var _typoUnitDefaults = { size:'px', lineHeight:'', letterSpacing:'px', wordSpacing:'px' };
14 function typoCssVarsForEl(el, obj, prefix) {
15 if (!obj || typeof obj !== 'object') return;
16 Object.keys(_typoKeys).forEach(function (k) {
17 var v = obj[k]; if (v === undefined || v === '') return;
18 var prop = _typoKeys[k];
19 var base = k.replace(/Desktop|Tablet|Mobile/, '');
20 var uKey = _typoUnits[base];
21 if (uKey && typeof v === 'number') v = v + (obj[uKey] || _typoUnitDefaults[base] || '');
22 el.style.setProperty(prefix + prop, v);
23 });
24 }
25
26 function calcCompound(principal, monthly, rateAnn, years, freq) {
27 var n = freq;
28 var r = rateAnn / 100;
29 var rows = [];
30 var balance = principal;
31 for (var y = 1; y <= years; y++) {
32 balance = balance * Math.pow(1 + r / n, n) + monthly * 12 * ((Math.pow(1 + r / n, n) - 1) / (r / n));
33 var contributed = principal + monthly * 12 * y;
34 var interest = Math.max(0, balance - contributed);
35 rows.push({ year: y, balance: balance, contributed: contributed, interest: interest });
36 }
37 return rows;
38 }
39
40 function fmt(sym, n) { return sym + Math.round(n).toLocaleString(); }
41
42 function init(app) {
43 var a;
44 try { a = JSON.parse(app.getAttribute('data-opts')); } catch (e) { return; }
45
46 var sym = a.currency || '$';
47 var accent = a.accentColor || '#6c3fb5';
48
49 app.style.paddingTop = (a.paddingTop || 60) + 'px';
50 app.style.paddingBottom = (a.paddingBottom || 60) + 'px';
51 if (a.sectionBg) app.style.background = a.sectionBg;
52 app.innerHTML = '';
53
54 /* ── Typography CSS vars ── */
55 typoCssVarsForEl(app, a.typoTitle, '--bkcic-title-');
56 typoCssVarsForEl(app, a.typoResult, '--bkcic-result-');
57
58 var wrap = document.createElement('div');
59 wrap.className = 'bkbg-cic-wrap';
60 wrap.style.maxWidth = (a.maxWidth || 580) + 'px';
61 wrap.style.borderRadius = (a.cardRadius || 16) + 'px';
62 wrap.style.background = a.cardBg || '#fff';
63 app.appendChild(wrap);
64
65 // Header
66 if (a.showTitle || a.showSubtitle) {
67 var header = document.createElement('div');
68 header.className = 'bkbg-cic-header';
69 if (a.showTitle && a.title) {
70 var t = document.createElement('div');
71 t.className = 'bkbg-cic-title';
72 t.textContent = a.title;
73 if (a.titleColor) t.style.color = a.titleColor;
74 header.appendChild(t);
75 }
76 if (a.showSubtitle && a.subtitle) {
77 var s = document.createElement('div');
78 s.className = 'bkbg-cic-subtitle';
79 s.textContent = a.subtitle;
80 if (a.subtitleColor) s.style.color = a.subtitleColor;
81 header.appendChild(s);
82 }
83 wrap.appendChild(header);
84 }
85
86 var ir = (a.inputRadius || 8) + 'px';
87 var ib = '1.5px solid ' + (a.inputBorder || '#e5e7eb');
88
89 function makeField(labelText, inputEl) {
90 var f = document.createElement('div');
91 f.className = 'bkbg-cic-field';
92 var lbl = document.createElement('label');
93 lbl.className = 'bkbg-cic-label';
94 lbl.textContent = labelText;
95 if (a.labelColor) lbl.style.color = a.labelColor;
96 f.appendChild(lbl);
97 f.appendChild(inputEl);
98 return f;
99 }
100
101 function makeInput(val, min) {
102 var inp = document.createElement('input');
103 inp.type = 'number';
104 inp.className = 'bkbg-cic-input';
105 inp.value = val;
106 inp.min = String(min || 0);
107 inp.style.borderRadius = ir;
108 inp.style.border = ib;
109 return inp;
110 }
111
112 var principalInput = makeInput(a.defaultPrincipal || 10000);
113 var monthlyInput = makeInput(a.defaultMonthly || 200);
114 var rateInput = makeInput(a.defaultRate || 7);
115 var yearsInput = makeInput(a.defaultYears || 20, 1);
116
117 var row1 = document.createElement('div'); row1.className = 'bkbg-cic-row';
118 row1.appendChild(makeField('Initial Investment (' + sym + ')', principalInput));
119 row1.appendChild(makeField('Monthly Contribution (' + sym + ')', monthlyInput));
120 wrap.appendChild(row1);
121
122 var row2 = document.createElement('div'); row2.className = 'bkbg-cic-row';
123 row2.appendChild(makeField('Annual Rate (%)', rateInput));
124 row2.appendChild(makeField('Years', yearsInput));
125 wrap.appendChild(row2);
126
127 // Frequency select
128 var freqWrap = document.createElement('div');
129 freqWrap.className = 'bkbg-cic-freq-field';
130 var freqLbl = document.createElement('label');
131 freqLbl.className = 'bkbg-cic-label';
132 freqLbl.textContent = 'Compound Frequency';
133 if (a.labelColor) freqLbl.style.color = a.labelColor;
134 var freqSel = document.createElement('select');
135 freqSel.className = 'bkbg-cic-select';
136 freqSel.style.borderRadius = ir;
137 freqSel.style.border = ib;
138 [['1','Annually (1×/year)'],['2','Semi-annually (2×/yr)'],['4','Quarterly (4×/yr)'],['12','Monthly (12×/yr)'],['365','Daily (365×/yr)']].forEach(function(o) {
139 var opt = document.createElement('option');
140 opt.value = o[0];
141 opt.textContent = o[1];
142 if (o[0] === String(a.defaultFrequency || '12')) opt.selected = true;
143 freqSel.appendChild(opt);
144 });
145 freqWrap.appendChild(freqLbl);
146 freqWrap.appendChild(freqSel);
147 wrap.appendChild(freqWrap);
148
149 // Result card
150 var resultCard = document.createElement('div');
151 resultCard.className = 'bkbg-cic-result';
152 resultCard.style.background = a.resultBg || accent;
153 resultCard.style.borderRadius = ir;
154
155 var resultLabel = document.createElement('div');
156 resultLabel.className = 'bkbg-cic-result-label';
157 resultLabel.style.color = a.resultColor || '#fff';
158
159 var resultAmount = document.createElement('div');
160 resultAmount.className = 'bkbg-cic-result-amount';
161 resultAmount.style.color = a.resultColor || '#fff';
162
163 var breakdown = document.createElement('div');
164 breakdown.className = 'bkbg-cic-result-breakdown';
165
166 var contribDiv = document.createElement('div');
167 var contribSLbl = document.createElement('div'); contribSLbl.className = 'bkbg-cic-result-sub-label'; contribSLbl.style.color = a.resultColor||'#fff'; contribSLbl.textContent = 'Principal + Contributions';
168 var contribSVal = document.createElement('div'); contribSVal.className = 'bkbg-cic-result-sub-value'; contribSVal.style.color = a.principalColor||'#93c5fd';
169 contribDiv.appendChild(contribSLbl); contribDiv.appendChild(contribSVal);
170
171 var interestDiv = document.createElement('div');
172 var interestSLbl = document.createElement('div'); interestSLbl.className = 'bkbg-cic-result-sub-label'; interestSLbl.style.color = a.resultColor||'#fff'; interestSLbl.textContent = 'Interest Earned';
173 var interestSVal = document.createElement('div'); interestSVal.className = 'bkbg-cic-result-sub-value'; interestSVal.style.color = a.interestColor||'#6ee7b7';
174 interestDiv.appendChild(interestSLbl); interestDiv.appendChild(interestSVal);
175
176 breakdown.appendChild(contribDiv);
177 breakdown.appendChild(interestDiv);
178 resultCard.appendChild(resultLabel);
179 resultCard.appendChild(resultAmount);
180 resultCard.appendChild(breakdown);
181 wrap.appendChild(resultCard);
182
183 // Bar
184 var barWrap = document.createElement('div'); barWrap.className = 'bkbg-cic-bar-wrap';
185 var barTrack = document.createElement('div'); barTrack.className = 'bkbg-cic-bar-track'; barTrack.style.background = a.tableBorder || '#e5e7eb';
186 var barInner = document.createElement('div'); barInner.className = 'bkbg-cic-bar-inner';
187 var barPrin = document.createElement('div'); barPrin.className = 'bkbg-cic-bar-principal'; barPrin.style.background = a.principalColor || '#3b82f6';
188 var barInt = document.createElement('div'); barInt.className = 'bkbg-cic-bar-interest'; barInt.style.background = a.interestColor || '#10b981';
189 barInner.appendChild(barPrin); barInner.appendChild(barInt);
190 barTrack.appendChild(barInner);
191 var barLegend = document.createElement('div'); barLegend.className = 'bkbg-cic-bar-legend';
192 var legPrin = document.createElement('span'); var legInt = document.createElement('span');
193 barLegend.appendChild(legPrin); barLegend.appendChild(legInt);
194 barWrap.appendChild(barTrack); barWrap.appendChild(barLegend);
195 wrap.appendChild(barWrap);
196
197 // Table
198 var tableWrap = null, tbody = null;
199 if (a.showTable) {
200 tableWrap = document.createElement('div');
201 tableWrap.className = 'bkbg-cic-table-wrap';
202 var table = document.createElement('table');
203 table.className = 'bkbg-cic-table';
204 if (a.tableBg) table.style.background = a.tableBg;
205 var thead = document.createElement('thead');
206 var headRow = document.createElement('tr');
207 ['Year','Balance','Contributed','Interest Earned'].forEach(function(h) {
208 var th = document.createElement('th');
209 th.textContent = h;
210 if (a.tableHeaderBg) th.style.background = a.tableHeaderBg;
211 if (a.labelColor) th.style.color = a.labelColor;
212 if (a.tableBorder) th.style.borderBottomColor = a.tableBorder;
213 headRow.appendChild(th);
214 });
215 thead.appendChild(headRow);
216 table.appendChild(thead);
217 tbody = document.createElement('tbody');
218 table.appendChild(tbody);
219 tableWrap.appendChild(table);
220 wrap.appendChild(tableWrap);
221 }
222
223 function recalculate() {
224 var principal = parseFloat(principalInput.value) || 0;
225 var monthly = parseFloat(monthlyInput.value) || 0;
226 var rate = parseFloat(rateInput.value) || 0;
227 var years = Math.max(1, parseInt(yearsInput.value) || 1);
228 var freq = parseInt(freqSel.value) || 12;
229
230 var rows = calcCompound(principal, monthly, rate, years, freq);
231 var last = rows[rows.length - 1] || { balance: 0, contributed: principal, interest: 0 };
232 var contributed = principal + monthly * 12 * years;
233 var interest = Math.max(0, last.balance - contributed);
234
235 resultLabel.textContent = 'Future Value after ' + years + ' years';
236 resultAmount.textContent = fmt(sym, last.balance);
237 contribSVal.textContent = fmt(sym, contributed);
238 interestSVal.textContent = fmt(sym, interest);
239
240 var pct = last.balance > 0 ? contributed / last.balance * 100 : 100;
241 barPrin.style.width = pct + '%';
242 legPrin.textContent = '■ Principal & Contributions (' + Math.round(pct) + '%)';
243 legInt.textContent = '■ Interest (' + Math.round(100 - pct) + '%)';
244
245 if (tbody) {
246 tbody.innerHTML = '';
247 var limit = a.tableRows || 10;
248 rows.slice(0, limit).forEach(function(row) {
249 var tr = document.createElement('tr');
250 [
251 { val: row.year, color: a.labelColor || '#374151', bold: false },
252 { val: fmt(sym, row.balance), color: '#1f2937', bold: true },
253 { val: fmt(sym, row.contributed), color: a.principalColor || '#3b82f6', bold: false },
254 { val: fmt(sym, row.interest), color: a.interestColor || '#10b981', bold: false }
255 ].forEach(function(cell) {
256 var td = document.createElement('td');
257 td.textContent = cell.val;
258 td.style.color = cell.color;
259 if (cell.bold) td.style.fontWeight = '700';
260 if (a.tableBorder) td.style.borderBottomColor = a.tableBorder;
261 tr.appendChild(td);
262 });
263 tbody.appendChild(tr);
264 });
265 }
266 }
267
268 [principalInput, monthlyInput, rateInput, yearsInput, freqSel].forEach(function(el) {
269 el.addEventListener('input', recalculate);
270 el.addEventListener('change', recalculate);
271 });
272
273 recalculate();
274 }
275
276 document.querySelectorAll('.bkbg-cic-app').forEach(init);
277 })();
278