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 / loan-affordability / frontend.js

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

199 lines 9.6 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 decoration: 'text-decoration', transform: 'text-transform',
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 letterSpacing: 'letter-spacing', wordSpacing: 'word-spacing'
10 };
11 function typoCssVarsForEl(el, typo, prefix) {
12 if (!typo || typeof typo !== 'object') return;
13 Object.keys(typo).forEach(function (k) {
14 var v = typo[k]; if (v === '' || v == null) return;
15 var css = _typoKeys[k]; if (!css) return;
16 if ((k === 'letterSpacing' || k === 'wordSpacing') && typeof v === 'number') v = v + 'px';
17 if ((/^(sizeDesktop|sizeTablet|sizeMobile)$/).test(k) && typeof v === 'number') v = v + 'px';
18 el.style.setProperty(prefix + css, '' + v);
19 });
20 }
21
22 function calcAffordability(income, monthlyDebts, rate, years, downPct, feRatio, beRatio, incomeShare, paymentMode) {
23 var monthlyIncome = income / 12;
24 var maxFront = monthlyIncome * (feRatio / 100);
25 var maxBack = monthlyIncome * (beRatio / 100) - monthlyDebts;
26 var maxDti = Math.max(0, Math.min(maxFront, maxBack));
27 var maxShare = Math.max(0, monthlyIncome * ((incomeShare || 30) / 100));
28 var mode = paymentMode || 'min';
29 var maxPmt;
30
31 if (mode === 'incomeShare') {
32 maxPmt = maxShare;
33 } else if (mode === 'dti') {
34 maxPmt = maxDti;
35 } else {
36 maxPmt = Math.min(maxDti, maxShare);
37 }
38 var r = rate / 100 / 12;
39 var n = years * 12;
40 var maxLoan = r === 0 ? maxPmt * n : maxPmt * ((1 - Math.pow(1 + r, -n)) / r);
41 var downAmount = maxLoan > 0 ? (maxLoan / (1 - downPct / 100)) * (downPct / 100) : 0;
42 var maxHome = maxLoan + downAmount;
43 var dti = monthlyIncome > 0 ? ((maxPmt + monthlyDebts) / monthlyIncome) * 100 : 0;
44 return { maxHome: Math.max(0, maxHome), maxLoan: Math.max(0, maxLoan), maxMonthly: maxPmt, downAmount: Math.max(0, downAmount), dti: dti };
45 }
46
47 function fmtMoney(val, cur, pos) {
48 var s = Math.round(val).toLocaleString('en-US');
49 return pos === 'after' ? s + cur : cur + s;
50 }
51
52 function esc(s) { return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }
53
54 function buildApp(app) {
55 var opts;
56 try { opts = JSON.parse(app.getAttribute('data-opts') || '{}'); } catch (e) { return; }
57 var o = opts;
58 var accent = o.accentColor || '#6c3fb5';
59 var cur = o.currency || '$';
60 var pos = o.currencyPos || 'before';
61
62 app.style.paddingTop = (o.paddingTop || 60) + 'px';
63 app.style.paddingBottom = (o.paddingBottom || 60) + 'px';
64 if (o.sectionBg) app.style.background = o.sectionBg;
65
66 typoCssVarsForEl(app, o.titleTypo, '--bkbg-la-tt-');
67 typoCssVarsForEl(app, o.resultTypo, '--bkbg-la-rt-');
68
69 var maxW = (o.maxWidth || 680) + 'px';
70
71 /* Header */
72 if (o.showTitle || o.showSubtitle) {
73 var hdr = document.createElement('div');
74 hdr.style.cssText = 'text-align:center;margin-bottom:32px;max-width:' + maxW + ';margin-left:auto;margin-right:auto';
75 if (o.showTitle && o.title) {
76 var ht = document.createElement('h3');
77 ht.className = 'bkbg-la-title';
78 ht.textContent = o.title;
79 ht.style.color = o.titleColor || '#1e1b4b';
80 hdr.appendChild(ht);
81 }
82 if (o.showSubtitle && o.subtitle) {
83 var hs = document.createElement('p');
84 hs.className = 'bkbg-la-subtitle';
85 hs.textContent = o.subtitle;
86 hs.style.color = o.subtitleColor || '#6b7280';
87 hdr.appendChild(hs);
88 }
89 app.appendChild(hdr);
90 }
91
92 /* Card */
93 var card = document.createElement('div');
94 card.className = 'bkbg-la-card';
95 card.style.cssText = 'background:' + (o.cardBg || '#fff') + ';border-radius:' + (o.cardRadius || 16) + 'px;padding:32px;max-width:' + maxW + ';margin:0 auto;box-shadow:0 4px 24px rgba(0,0,0,0.06)';
96 app.appendChild(card);
97
98 /* State */
99 var state = {
100 income: o.defaultIncome || 75000,
101 debts: o.defaultDebts || 500,
102 rate: o.defaultRate || 6.5,
103 term: o.defaultTerm || 30,
104 down: o.defaultDown || 20
105 };
106
107 /* Hero result box */
108 var hero = document.createElement('div');
109 hero.className = 'bkbg-la-hero';
110 hero.style.cssText = 'background:' + (o.resultBg || '#f5f3ff') + ';border:2px solid ' + (o.resultBorder || '#ede9fe') + ';border-radius:' + (o.cardRadius || 16) + 'px;padding:28px 32px;text-align:center;margin-bottom:24px';
111 card.appendChild(hero);
112
113 /* Input rows */
114 function makeRow(label, stateKey, pfx, sfx, step) {
115 var row = document.createElement('div');
116 row.className = 'bkbg-la-input-row';
117 var lbl = document.createElement('label');
118 lbl.textContent = label;
119 lbl.style.cssText = 'font-size:14px;color:' + (o.labelColor || '#374151');
120 var right = document.createElement('div');
121 right.style.cssText = 'display:flex;align-items:center;gap:6px';
122 if (pfx) {
123 var ps = document.createElement('span');
124 ps.textContent = pfx;
125 ps.style.cssText = 'color:' + (o.subtitleColor || '#6b7280') + ';font-size:13px';
126 right.appendChild(ps);
127 }
128 var inp = document.createElement('input');
129 inp.type = 'number'; inp.value = state[stateKey]; inp.min = '0'; inp.step = step || '1';
130 inp.style.cssText = 'width:110px;text-align:right;border:1px solid #d1d5db;border-radius:' + (o.inputRadius || 8) + 'px;padding:5px 8px;font-size:14px;box-sizing:border-box';
131 inp.addEventListener('input', function () { state[stateKey] = parseFloat(inp.value) || 0; update(); });
132 right.appendChild(inp);
133 if (sfx) {
134 var ss = document.createElement('span');
135 ss.textContent = sfx;
136 ss.style.cssText = 'color:' + (o.subtitleColor || '#6b7280') + ';font-size:13px';
137 right.appendChild(ss);
138 }
139 row.appendChild(lbl);
140 row.appendChild(right);
141 card.appendChild(row);
142 }
143
144 makeRow('Annual Gross Income', 'income', cur, null, '1000');
145 makeRow('Monthly Existing Debts', 'debts', cur, null, '50');
146 makeRow('Interest Rate', 'rate', null, '%', '0.1');
147 makeRow('Loan Term', 'term', null, 'years', '5');
148 makeRow('Down Payment', 'down', null, '%', '1');
149
150 /* Stat row */
151 var statRow = document.createElement('div');
152 statRow.className = 'bkbg-la-stat-row';
153 card.appendChild(statRow);
154
155 function update() {
156 var res = calcAffordability(
157 state.income,
158 state.debts,
159 state.rate,
160 state.term,
161 state.down,
162 o.frontEndRatio || 28,
163 o.backEndRatio || 43,
164 o.incomeShareLimit || 30,
165 o.monthlyPaymentMode || 'min'
166 );
167
168 /* Update hero */
169 hero.innerHTML =
170 '<div style="font-size:12px;color:' + (o.subtitleColor || '#6b7280') + ';font-weight:600;text-transform:uppercase;letter-spacing:0.06em">Maximum Home Price</div>' +
171 '<div class="bkbg-la-result-hero" style="color:' + (o.maxHomeColor || accent) + '">' +
172 esc(fmtMoney(res.maxHome, cur, pos)) + '</div>' +
173 '<div style="font-size:13px;color:' + (o.subtitleColor || '#6b7280') + '">with ' + state.down + '% down · ' + state.rate + '% rate · ' + state.term + ' yr term</div>';
174
175 /* Stat cards */
176 statRow.innerHTML = '';
177 var stats = [];
178 if (o.showMaxLoan !== false) stats.push({ val: fmtMoney(res.maxLoan, cur, pos), lbl: 'Max Loan Amount', color: o.maxLoanColor || '#3b82f6' });
179 if (o.showMonthly !== false) stats.push({ val: fmtMoney(res.maxMonthly, cur, pos), lbl: 'Max Monthly Payment', color: o.monthlyColor || '#10b981' });
180 if (o.showDTI !== false) stats.push({ val: res.dti.toFixed(1) + '%', lbl: 'DTI Ratio', color: o.dtiColor || '#f59e0b' });
181 if (o.showDownPayment !== false) stats.push({ val: fmtMoney(res.downAmount, cur, pos), lbl: 'Down Payment', color: o.labelColor || '#374151' });
182
183 stats.forEach(function (st) {
184 var sc = document.createElement('div');
185 sc.className = 'bkbg-la-stat';
186 sc.style.cssText = 'background:' + (o.statCardBg || '#f9fafb') + ';border-radius:12px;padding:20px 16px;text-align:center;flex:1;min-width:120px;box-sizing:border-box';
187 sc.innerHTML =
188 '<div class="bkbg-la-stat-val" style="color:' + st.color + '">' + esc(st.val) + '</div>' +
189 '<div class="bkbg-la-stat-lbl" style="font-size:12px;color:' + (o.subtitleColor || '#6b7280') + ';margin-top:6px">' + esc(st.lbl) + '</div>';
190 statRow.appendChild(sc);
191 });
192 }
193
194 update();
195 }
196
197 document.querySelectorAll('.bkbg-la-app').forEach(buildApp);
198 })();
199