Glas Theme Coming Next

This commit is contained in:
2026-03-13 16:05:38 -04:00
parent ac9420c812
commit 591e4155fc
4 changed files with 388 additions and 73 deletions

View File

@@ -351,7 +351,7 @@ function update() {
const subParts = [`${users} × ${fmt(baseUserRate)}/user (${byol ? 'BYOL' : 'M365 Incl.'})`];
if (addExtHours) subParts.push(`+ ${fmt(userExt)}/mo ext. hrs`);
if (addPWM) subParts.push(`+ ${fmt(userPWM)}/mo 1Password`);
if (addINKY) subParts.push(`+ ${fmt(userINKY)}/mo INKY`);
if (addINKY) subParts.push(`+ ${fmt(userINKY)}/mo INKY Pro upgrade`);
if (addZT) subParts.push(`+ ${fmt(userZT)}/mo Zero Trust`);
sub.innerHTML = subParts.join('<br>');
}
@@ -376,6 +376,7 @@ function update() {
if (voipTotal > 0) getEl('sl-voip-val').textContent = fmt(voipTotal);
const slAdminEl = getEl('sl-admin');
const slAdminValEl = getEl('sl-admin-val');
const slAdminSubEl = getEl('sl-admin-sub');
if (adminWaived) {
slAdminEl?.classList.add('sl-admin-waived');
if (slAdminValEl) slAdminValEl.innerHTML =
@@ -384,6 +385,13 @@ function update() {
slAdminEl?.classList.remove('sl-admin-waived');
if (slAdminValEl) slAdminValEl.textContent = fmt(adminFeeNet);
}
if (slAdminSubEl) {
const adminParts = [`Base ${fmt(siteAdminBase)}/mo`];
if (ztActive) adminParts.push(`+ ${fmt(ADMIN_FEE_ZT)}/mo Zero Trust supplement`);
if (addPWM && admin1PWM > 0) adminParts.push(`+ ${fmt(admin1PWM)}/mo 1Password admin`);
slAdminSubEl.classList.remove('hidden');
slAdminSubEl.innerHTML = adminParts.join('<br>');
}
// MRR + totals — show effective MRR (after term discount) as the headline number
getEl('mrrDisplay').textContent = fmt(effectiveMrr);
@@ -546,7 +554,6 @@ function onWaiveToggle() {
// Calls updateSectionSummaries() to show/hide summary badges.
// Map: section ID → collapsible IDs that should auto-expand when section opens
const _sectionCollapsibles = {
'sec-01': ['adminCovered'],
'sec-02': ['userIncluded', 'addonsA'],
'sec-03': ['endpointIncluded', 'addonsB'],
'sec-04': ['serverIncluded'],
@@ -849,6 +856,7 @@ function stepInput(id, delta) {
// --- AUTO-SAVE / RESTORE ---
const SAVE_KEY = 'svs-msp-quote-v1';
const QUOTE_REF_KEY = 'svs-msp-quote-ref';
function saveState() {
try {
@@ -886,6 +894,50 @@ function debouncedSave() {
_saveTimer = setTimeout(saveState, 400);
}
function syncBodyScrollLock() {
const panelOpen = document.getElementById('mobileQuotePanel')?.classList.contains('open');
const modalOpen = document.getElementById('resetConfirmModal')?.classList.contains('open');
document.body.style.overflow = (panelOpen || modalOpen) ? 'hidden' : '';
}
function openResetConfirm() {
const modal = document.getElementById('resetConfirmModal');
if (!modal) return;
modal.classList.add('open');
modal.setAttribute('aria-hidden', 'false');
syncBodyScrollLock();
document.getElementById('resetConfirmCancel')?.focus();
}
function closeResetConfirm() {
const modal = document.getElementById('resetConfirmModal');
if (!modal) return;
modal.classList.remove('open');
modal.setAttribute('aria-hidden', 'true');
syncBodyScrollLock();
}
function confirmResetQuote() {
clearTimeout(_saveTimer);
try {
localStorage.removeItem(SAVE_KEY);
localStorage.removeItem(QUOTE_REF_KEY);
} catch (e) {
console.warn('confirmResetQuote: failed to clear saved quote state', e);
}
closeResetConfirm();
window.location.reload();
}
document.addEventListener('keydown', function(e) {
const modalOpen = document.getElementById('resetConfirmModal')?.classList.contains('open');
if (e.key === 'Escape' && modalOpen) {
closeResetConfirm();
e.preventDefault();
e.stopImmediatePropagation();
}
});
// ── restoreState() ───────────────────────────────────────────────
// Restores form state from localStorage on page load.
// SAVE_KEY = 'svs-msp-quote-v1'.
@@ -966,7 +1018,7 @@ function printInvoice() {
row(`User Package — ${pkg}`, `${q.users} user${q.users!==1?'s':''} × ${fmt(q.baseUserRate)}/mo`, fmt(q.userBase));
if (q.userExt > 0) row(`↳ Extended Hours (+${fmt(ADDON_EXT_HOURS)}/user)`, '', fmt(q.userExt), true);
if (q.userPWM > 0) row(`↳ 1Password Business (+${fmt(ADDON_1PASSWORD)}/user)`, '', fmt(q.userPWM), true);
if (q.userINKY > 0) row(`↳ Inky Email Security (+${fmt(ADDON_INKY)}/user)`, '', fmt(q.userINKY), true);
if (q.userINKY > 0) row(`↳ INKY Pro Upgrade (+${fmt(ADDON_INKY)}/user)`, '', fmt(q.userINKY), true);
if (q.userZT > 0) row(`↳ Zero Trust User (+${fmt(ADDON_ZERO_TRUST_USER)}/user)`, '', fmt(q.userZT), true);
}
if (q.endpoints > 0) {
@@ -1014,7 +1066,7 @@ function printInvoice() {
feat('Licensing Model', true, q.byol ? 'BYOL — Bring Your Own License' : 'M365 Premium Included');
feat('Extended Help Desk Hours', q.addExtHours, q.addExtHours ? `+${fmt(ADDON_EXT_HOURS)}/user/mo` : '');
feat('1Password Business', q.addPWM, q.addPWM ? `+${fmt(ADDON_1PASSWORD)}/user/mo` : '');
feat('INKY Pro Email Security', q.addINKY, q.addINKY ? `+${fmt(ADDON_INKY)}/user/mo` : '');
feat('INKY Pro Upgrade', q.addINKY, q.addINKY ? `+${fmt(ADDON_INKY)}/user/mo` : '');
feat('Zero Trust User Access', q.addZT, q.addZT ? `+${fmt(ADDON_ZERO_TRUST_USER)}/user/mo` : '');
feat('USB Device Blocking', q.addUSB, q.addUSB ? `+${fmt(ADDON_USB_BLOCKING)}/endpoint/mo` : '');
feat('Bare Metal Backup', q.addBMB, q.addBMB ? `+${fmt(ADDON_BARE_METAL_BACKUP)}/endpoint/mo` : '');
@@ -1280,22 +1332,22 @@ async function initQuote() {
const year = now.getFullYear();
const month = months[now.getMonth()];
const dateStr = `${year}${String(now.getMonth()+1).padStart(2,'0')}${String(now.getDate()).padStart(2,'0')}`;
const savedRef = localStorage.getItem('svs-msp-quote-ref');
const savedRef = localStorage.getItem(QUOTE_REF_KEY);
let quoteRef;
if (savedRef) {
// Regenerate if the baked-in date is older than 30 days
const m = savedRef.match(/^SVS-(\d{4})(\d{2})(\d{2})-/);
const refDate = m ? new Date(+m[1], +m[2] - 1, +m[3]) : null;
const ageMs = refDate ? now - refDate : Infinity;
if (ageMs > 30 * 24 * 60 * 60 * 1000) {
quoteRef = `SVS-${dateStr}-${String(Math.floor(Math.random()*9000)+1000)}`;
localStorage.setItem('svs-msp-quote-ref', quoteRef);
} else {
quoteRef = savedRef;
}
const refDate = m ? new Date(+m[1], +m[2] - 1, +m[3]) : null;
const ageMs = refDate ? now - refDate : Infinity;
if (ageMs > 30 * 24 * 60 * 60 * 1000) {
quoteRef = `SVS-${dateStr}-${String(Math.floor(Math.random()*9000)+1000)}`;
localStorage.setItem(QUOTE_REF_KEY, quoteRef);
} else {
quoteRef = savedRef;
}
} else {
quoteRef = `SVS-${dateStr}-${String(Math.floor(Math.random()*9000)+1000)}`;
localStorage.setItem('svs-msp-quote-ref', quoteRef);
localStorage.setItem(QUOTE_REF_KEY, quoteRef);
}
const quoteRefEl = document.getElementById('quoteRef');
if (quoteRefEl) quoteRefEl.textContent = quoteRef;
@@ -1341,7 +1393,7 @@ initQuote();
var panel = document.getElementById('mobileQuotePanel');
if (panel) {
panel.classList.add('open');
document.body.style.overflow = 'hidden';
syncBodyScrollLock();
}
};
@@ -1349,7 +1401,7 @@ initQuote();
var panel = document.getElementById('mobileQuotePanel');
if (panel) {
panel.classList.remove('open');
document.body.style.overflow = '';
syncBodyScrollLock();
}
};
@@ -1423,10 +1475,12 @@ initQuote();
syncEl('nudgeCounter');
syncEl('sl-users-sub');
syncEl('sl-endpoints-sub');
syncEl('sl-admin-sub');
syncClass('sl-users');
syncClass('sl-users-sub');
syncClass('sl-endpoints');
syncClass('sl-endpoints-sub');
syncClass('sl-admin-sub');
syncClass('sl-servers');
syncClass('sl-zt');
syncClass('sl-voip');
@@ -1452,6 +1506,7 @@ initQuote();
syncEl('adminWaivedAmt');
syncStyle('sl-users-sub');
syncStyle('sl-endpoints-sub');
syncStyle('sl-admin-sub');
syncStyle('perUserRow');
syncChecked('hstToggle');
// Pill MRR — show effective MRR with label