← back to Sdcc Mockups
extension-v0/popup.js
139 lines
// SDCC Borrower Helper — popup logic
// Everything runs in this isolated extension context. No fetch() calls. No remote scripts.
// All inputs and computations are local. localStorage is per-extension and isolated from page.
// ----- Tab switcher -----
document.querySelectorAll('.tab').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
btn.classList.add('active');
document.getElementById('panel-' + btn.dataset.tab).classList.add('active');
});
});
// ----- Active page scan -----
async function scanActiveTab() {
try {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
const url = new URL(tab.url || 'about:blank');
document.getElementById('scan-host').textContent = url.host || 'about:blank';
// Check if this is a host we care about
const FEDERAL_HOSTS = {
'studentaid.gov': 'Federal Student Aid',
'nslds.ed.gov': 'NSLDS',
'mohela.com': 'MOHELA (servicer)',
'aidvantage.com': 'Aidvantage (servicer)',
'nelnet.com': 'Nelnet (servicer)',
'edfinancial.com': 'EdFinancial (servicer)',
};
let hostKey = Object.keys(FEDERAL_HOSTS).find(h => url.host.endsWith(h));
const data = document.getElementById('scan-data');
const out = document.getElementById('scan-out');
if (!hostKey) {
data.innerHTML = '<div>page<span>not federal</span></div><div>action<span>—</span></div>';
out.className = 'result show warn';
out.innerHTML = '<h3>Not on a recognized federal site</h3>Open <a href="https://studentaid.gov" target="_blank">studentaid.gov</a> or your servicer site to see live data points.';
return;
}
data.innerHTML =
'<div>page<span>' + FEDERAL_HOSTS[hostKey] + '</span></div>' +
'<div>path<span>' + (url.pathname.slice(0, 30) + (url.pathname.length > 30 ? '…' : '')) + '</span></div>' +
'<div>scan mode<span>passive read</span></div>';
out.className = 'result show good';
out.innerHTML = '<h3>Active on ' + FEDERAL_HOSTS[hostKey] + '</h3>' +
'<ul>' +
'<li>Content script attached for read-only scanning</li>' +
'<li>Looking for: loan totals, IDR plan, servicer name, next-payment date</li>' +
'<li>All findings stay in this popup. Nothing leaves your machine.</li>' +
'</ul>';
} catch (e) {
document.getElementById('scan-out').className = 'result show bad';
document.getElementById('scan-out').innerHTML = '<h3>Scan blocked</h3>' + (e.message || 'unknown');
}
}
document.getElementById('scan-btn').addEventListener('click', scanActiveTab);
scanActiveTab();
// ----- PSLF eligibility -----
document.getElementById('p-go').addEventListener('click', () => {
const loan = document.getElementById('p-loan').value;
const emp = document.getElementById('p-emp').value;
const yrs = +document.getElementById('p-yrs').value;
const pay = +document.getElementById('p-pay').value;
const out = document.getElementById('p-out');
let cls = 'good', title = 'Eligible — track to forgiveness', body = '';
if (loan === 'private') {
cls = 'bad'; title = 'Not eligible — private loans';
body = '<ul><li>PSLF only covers federal Direct loans.</li><li>Consolidate to Direct first if you want to qualify.</li><li>Consolidating resets your qualifying-payment count.</li></ul>';
} else if (loan === 'ffel-uc' || loan === 'perkins') {
cls = 'warn'; title = 'Eligible after consolidation';
body = '<ul><li>Consolidate FFEL/Perkins to Direct via studentaid.gov.</li><li>Consolidation generally resets payment count to 0.</li><li>Verify against the Account Adjustment rules first.</li></ul>';
} else if (emp === 'forprofit') {
cls = 'bad'; title = 'Not eligible — for-profit employer';
body = '<ul><li>For-profit work doesn\'t count, even at otherwise great companies.</li></ul>';
} else if (emp === 'other-np') {
cls = 'warn'; title = 'Probably eligible — verify employer';
body = '<ul><li>Only 501(c)(3) non-profits qualify automatically.</li><li>Other non-profits qualify only if they provide a "qualifying public service".</li><li>Verify on the <a href="https://studentaid.gov/pslf" target="_blank">PSLF Help Tool</a>.</li></ul>';
} else {
const remaining = 120 - pay;
if (remaining <= 0) {
title = 'You qualify NOW';
body = '<span class="big">120 / 120 payments</span><ul><li>File your PSLF application at <a href="https://studentaid.gov/pslf" target="_blank">studentaid.gov/pslf</a>.</li><li>Avg discharge: ~$73,400.</li><li>Keep paying until approved (overpayments refund).</li></ul>';
} else {
const yrsR = (remaining / 12).toFixed(1);
title = 'On track to forgiveness';
body = '<span class="big">' + pay + ' / 120 payments — ' + remaining + ' to go</span><ul><li>About <strong>' + yrsR + ' years</strong> at one qualifying payment per month.</li><li>File a PSLF Employment Certification Form annually.</li><li>If you change employers, recertify within 30 days.</li></ul>';
}
}
out.className = 'result show ' + cls;
out.innerHTML = '<h3>' + title + '</h3>' + body;
});
// ----- IDR comparison -----
document.getElementById('i-go').addEventListener('click', () => {
const agi = +document.getElementById('i-agi').value;
const fam = +document.getElementById('i-fam').value;
const fpl = 14580 + 5140 * (fam - 1);
const saveDisc = Math.max(0, agi - 2.25 * fpl);
const savePay = Math.round(saveDisc * 0.05 / 12);
const payeDisc = Math.max(0, agi - 1.5 * fpl);
const payePay = Math.round(payeDisc * 0.10 / 12);
const ibrPay = Math.round(payeDisc * 0.10 / 12);
const out = document.getElementById('i-out');
out.className = 'result show good';
out.innerHTML = '<h3>Side-by-side</h3><ul>' +
'<li><strong>SAVE</strong> (paused, litigation): $' + savePay + '/mo</li>' +
'<li><strong>PAYE</strong>: $' + payePay + '/mo</li>' +
'<li><strong>New IBR</strong>: $' + ibrPay + '/mo</li>' +
'</ul><p style="margin-top:8px;font-size:10px;color:#888">2026 federal poverty guidelines. Approximate. Real recommendation depends on loan balance + interest. Apply on <a href="https://studentaid.gov/idr" target="_blank">studentaid.gov/idr</a>.</p>';
});
// ----- Servicer letter translator -----
const PATTERNS = [
{ re: /recalculat/i, plain: 'Your monthly payment is being recalculated. Likely after annual recertification. Action: log into your servicer to confirm new amount.' },
{ re: /forbearance/i, plain: 'Your loan is going in or out of forbearance — payments paused but interest accrues. This is NOT forgiveness. Call to understand WHY.' },
{ re: /default/i, plain: 'You\'re being notified your loan is in default (270+ days past due). Action: call Default Resolution at 1-800-621-3115. Options: rehabilitation (9 payments) or consolidation.' },
{ re: /idr|income.?driven/i, plain: 'About your Income-Driven Repayment plan — usually annual recertification. Action: log into studentaid.gov, confirm plan + recert date.' },
{ re: /pslf|public service/i, plain: 'About PSLF. If a payment didn\'t qualify, you can dispute via the PSLF Reconsideration form at studentaid.gov/pslf.' },
{ re: /save/i, plain: 'About SAVE — currently paused by court order. You should NOT be making payments. Confirm forbearance status; don\'t let servicer push you off SAVE prematurely.' },
{ re: /garnish/i, plain: '⚠ Wage-garnishment notice. Government plans to take part of your paycheck. Action: you have 30 DAYS to request a hearing. Free help: CFPB + state borrower advocate.' },
{ re: /transfer/i, plain: 'Your loan is being transferred to a new servicer. Write down the new servicer name; account number may change.' },
];
document.getElementById('l-go').addEventListener('click', () => {
const txt = document.getElementById('l-in').value;
const out = document.getElementById('l-out');
if (!txt.trim()) { out.className = 'result show warn'; out.innerHTML = '<h3>Empty</h3>Paste a letter first.'; return; }
const matches = PATTERNS.filter(p => p.re.test(txt));
out.className = 'result show good';
if (matches.length === 0) {
out.innerHTML = '<h3>No common pattern</h3><p style="font-size:11px">No standard servicer template matched. Production version routes to a privacy-preserving local LLM (Phi-4 or DistilBERT) for one-off translation — no cloud call.</p>';
} else {
out.innerHTML = '<h3>Plain English</h3>' + matches.map(m => '<p style="margin-top:6px;font-size:11px">' + m.plain + '</p>').join('');
}
});