Before Theme Change

This commit is contained in:
2026-03-13 10:24:58 -04:00
parent 1663c69c63
commit ac9420c812
4 changed files with 411 additions and 213 deletions

View File

@@ -59,6 +59,17 @@
<div class="sidebar-title">SVS MSP — Live Quote</div>
<div class="sidebar-client" id="clientNameDisplay_m">Client Name</div>
</div>
<!-- Nudge Banner (mobile) -->
<div id="nudgeBanner_m" class="nudge-banner amber hidden">
<div class="nudge-header-row">
<span class="nudge-banner-label" style="margin-bottom:0;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" width="12" height="14" fill="currentColor" style="margin-right:6px;vertical-align:middle;"><path d="M272 384c9.6-31.9 29.5-59.1 49.2-86.2l0 0c5.2-7.1 10.4-14.2 15.4-21.4c19.8-28.5 31.4-63 31.4-100.3C368 78.8 289.2 0 192 0S16 78.8 16 176c0 37.3 11.6 71.9 31.4 100.3c5 7.2 10.2 14.3 15.4 21.4l0 0c19.8 27.1 39.7 54.4 49.2 86.2H272zM192 512c44.2 0 80-35.8 80-80V416H112v16c0 44.2 35.8 80 80 80zM112 352H272c0 0 0 0 0 0H112c0 0 0 0 0 0z"/></svg> Insight <span id="nudgeCounter_m" class="nudge-counter"></span></span>
<div class="nudge-nav-group">
<button onclick="cycleNudge(-1)" class="nudge-nav-btn" title="Previous"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg></button>
<button onclick="cycleNudge(1)" class="nudge-nav-btn" title="Next"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg></button>
</div>
</div>
<span id="nudgeText_m"></span>
</div>
<div class="sidebar-body">
<div id="sidebarLines_m">
<div class="sidebar-note hidden" id="sideNote-m365_m"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="14" height="14" fill="var(--green)" style="margin-right:6px;vertical-align:middle;flex-shrink:0;"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"/></svg> Bundled M365 saves client up to <strong id="m365SaveAmt_m" style="color:var(--green);"></strong>/mo vs retail licensing</div>
@@ -67,12 +78,12 @@
<span><span class="lbl-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" width="14" height="13" fill="currentColor" style="vertical-align:middle;"><path d="M144 0a80 80 0 1 1 0 160A80 80 0 1 1 144 0zM512 0a80 80 0 1 1 0 160A80 80 0 1 1 512 0zM0 298.7C0 239.8 47.8 192 106.7 192h42.7c15.9 0 31 3.5 44.6 9.7c-1.3 7.2-1.9 14.7-1.9 22.3c0 38.2 16.8 72.5 43.3 96c-.2 0-.4 0-.7 0H21.3C9.6 320 0 310.4 0 298.7zM405.3 320c-.2 0-.4 0-.7 0c26.6-23.5 43.3-57.8 43.3-96c0-7.6-.7-15-1.9-22.3c13.6-6.3 28.7-9.7 44.6-9.7h42.7C592.2 192 640 239.8 640 298.7c0 11.8-9.6 21.3-21.3 21.3H405.3zM224 224a96 96 0 1 1 192 0 96 96 0 1 1 -192 0zM128 485.3C128 411.7 187.7 352 261.3 352H378.7C452.3 352 512 411.7 512 485.3c0 14.7-11.9 26.7-26.7 26.7H154.7c-14.7 0-26.7-11.9-26.7-26.7z"/></svg></span> Users</span>
<span class="val" id="sl-users-val_m"></span>
</div>
<div class="sl-sub" style="display:none;" id="sl-users-sub_m"></div>
<div class="sl-sub hidden" id="sl-users-sub_m"></div>
<div class="sidebar-line hidden" id="sl-endpoints_m">
<span><span class="lbl-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" width="14" height="13" fill="currentColor" style="vertical-align:middle;"><path d="M64 0C28.7 0 0 28.7 0 64V352c0 35.3 28.7 64 64 64H240l-10.7 32H160c-17.7 0-32 14.3-32 32s14.3 32 32 32H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H346.7L336 416H512c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64H64zM512 64V352H64V64H512z"/></svg></span> Endpoints</span>
<span class="val" id="sl-endpoints-val_m"></span>
</div>
<div class="sl-sub" style="display:none;" id="sl-endpoints-sub_m"></div>
<div class="sl-sub hidden" id="sl-endpoints-sub_m"></div>
<div class="sidebar-line hidden" id="sl-servers_m">
<span><span class="lbl-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="13" height="13" fill="currentColor" style="vertical-align:middle;"><path d="M64 32C28.7 32 0 60.7 0 96v64c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zm280 72a24 24 0 1 1 0 48 24 24 0 1 1 0-48zm48 24a24 24 0 1 1 48 0 24 24 0 1 1 -48 0zM64 288c-35.3 0-64 28.7-64 64v64c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V352c0-35.3-28.7-64-64-64H64zm280 72a24 24 0 1 1 0 48 24 24 0 1 1 0-48zm56 24a24 24 0 1 1 48 0 24 24 0 1 1 -48 0z"/></svg></span> Servers</span>
<span class="val" id="sl-servers-val_m"></span>
@@ -131,7 +142,7 @@
<span>Annual Projection</span>
<span class="val" id="annualDisplay_m">$1,800</span>
</div>
<div class="sidebar-line" id="perUserRow_m" style="display:none;">
<div class="sidebar-line hidden" id="perUserRow_m">
<span>Avg. Cost Per User<br><small id="perUserBreakdown_m" class="per-user-cost-sub sidebar-note-mono hidden"></small></span>
<span class="val" id="perUserDisplay_m"></span>
</div>
@@ -160,18 +171,6 @@
</div>
</div>
<!-- Nudge Banner -->
<div id="nudgeBanner_m" class="nudge-banner amber hidden">
<div class="nudge-header-row">
<span class="nudge-banner-label" style="margin-bottom:0;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" width="12" height="14" fill="currentColor" style="margin-right:6px;vertical-align:middle;"><path d="M272 384c9.6-31.9 29.5-59.1 49.2-86.2l0 0c5.2-7.1 10.4-14.2 15.4-21.4c19.8-28.5 31.4-63 31.4-100.3C368 78.8 289.2 0 192 0S16 78.8 16 176c0 37.3 11.6 71.9 31.4 100.3c5 7.2 10.2 14.3 15.4 21.4l0 0c19.8 27.1 39.7 54.4 49.2 86.2H272zM192 512c44.2 0 80-35.8 80-80V416H112v16c0 44.2 35.8 80 80 80zM112 352H272c0 0 0 0 0 0H112c0 0 0 0 0 0z"/></svg> Insight <span id="nudgeCounter_m" class="nudge-counter"></span></span>
<div class="nudge-nav-group">
<button onclick="cycleNudge(-1)" class="nudge-nav-btn" title="Previous"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg></button>
<button onclick="cycleNudge(1)" class="nudge-nav-btn" title="Next"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg></button>
</div>
</div>
<span id="nudgeText_m"></span>
</div>
<div class="export-wrap">
<button class="btn-export" onclick="printInvoice()">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="14" height="14" fill="currentColor" style="margin-right:7px;vertical-align:middle;"><path d="M128 0C92.7 0 64 28.7 64 64v96h64V64H354.7L384 93.3V160h64V93.3c0-17-6.7-33.3-18.7-45.3L400 18.7C388 6.7 371.7 0 354.7 0H128zM384 352v32 64H128V384 352H384zm64 32h32c17.7 0 32-14.3 32-32V256c0-35.3-28.7-64-64-64H64c-35.3 0-64 28.7-64 64v96c0 17.7 14.3 32 32 32H64v64c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V384zm-16-88a24 24 0 1 1 0 48 24 24 0 1 1 0-48z"/></svg>
@@ -261,7 +260,7 @@
<div class="qs-fee-header">
<label class="qs-fee-label" for="oneTimeFee">Onboarding Fee</label>
<label class="qs-toggle-row qs-fee-waive">
<input type="checkbox" id="onboardingWaived" onchange="this.closest('.qs-fee-row').querySelector('#oneTimeFee').removeAttribute('data-manual'); update();">
<input type="checkbox" id="onboardingWaived" onchange="onWaiveToggle();">
<span class="qs-switch"></span>
<span class="qs-toggle-label">Waive</span>
</label>
@@ -275,7 +274,11 @@
</div>
<div class="sections-toolbar">
<button class="btn-toggle-all" id="toggleAllBtn" onclick="toggleAllSections()"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:middle;margin-right:5px;"><polyline points="6 9 12 15 18 9"/><polyline points="6 15 12 9 18 15"/></svg>Collapse All</button>
<button class="btn-toggle-all" id="toggleAllBtn" onclick="toggleAllSections()">
<span class="toggle-all-collapse-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:middle;margin-right:5px;"><polyline points="6 9 12 15 18 9"/><polyline points="6 15 12 9 18 15"/></svg></span>
<span class="toggle-all-expand-icon" style="display:none;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:middle;margin-right:5px;"><polyline points="6 15 12 9 18 15"/><polyline points="6 9 12 15 18 9"/></svg></span>
<span class="toggle-all-label">Collapse All</span>
</button>
</div>
<!-- ────────────────────────────────────────────────────────────
@@ -341,10 +344,10 @@
<!-- What's Covered collapsible -->
<div class="collapsible-header collapsible-header--mt16" onclick="toggleCollapsible('adminCovered')" tabindex="0" role="button" onkeydown="if(event.key==='Enter'||event.key===' '){toggleCollapsible('adminCovered');event.preventDefault();}">
<span class="collapsible-toggle" id="adminCovered-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></span>
<span class="collapsible-toggle open" id="adminCovered-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></span>
<span class="collapsible-label">What's Covered by the Admin Fee</span>
</div>
<div class="collapsible-body" id="adminCovered">
<div class="collapsible-body open" id="adminCovered">
<div class="feature-card-grid">
<div class="feature-card"><div class="feature-card-title"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" width="16" height="16" fill="var(--accent)" style="margin-right:8px;flex-shrink:0;vertical-align:middle;"><path d="M96 0C43 0 0 43 0 96V416c0 53 43 96 96 96H344.2c-1.5-9.5-2.2-19.2-2.2-29.1V384H96c-17.7 0-32-14.3-32-32V96c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32v84.2c19.4 6.7 37.3 17.3 52.9 30.6c5.3-2.7 11.3-4.8 17.1-4.8c23.7 0 42.9 19.2 42.9 42.9V272c16.8 10.4 32 23.4 44.8 38.8V176c0-53-43-96-96-96H416V96c0-53-43-96-96-96H96zM224 320a64 64 0 1 1 128 0 64 64 0 1 1 -128 0zm-32 64h192c17.7 0 32 14.3 32 32v32H160V416c0-17.7 14.3-32 32-32zM128 176c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H144c-8.8 0-16-7.2-16-16V176zm0 96c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H144c-8.8 0-16-7.2-16-16V272zm128-96c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H272c-8.8 0-16-7.2-16-16V176zm0 96c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H272c-8.8 0-16-7.2-16-16V272zm128-96c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H384c-8.8 0-16-7.2-16-16V176zM496 512a144 144 0 1 0 0-288 144 144 0 1 0 0 288zm0-96a48 48 0 1 1 0-96 48 48 0 1 1 0 96z"/></svg> Tenant & Identity Management</div><div class="feature-card-desc">Microsoft 365 / Entra ID tenant administration, user lifecycle, MFA enforcement, and conditional access policies.</div></div>
<div class="feature-card"><div class="feature-card-title"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" width="16" height="16" fill="var(--accent)" style="margin-right:8px;flex-shrink:0;vertical-align:middle;"><path d="M256 64H384v64H256V64zM240 0c-26.5 0-48 21.5-48 48v96c0 26.5 21.5 48 48 48h48v32H32c-17.7 0-32 14.3-32 32s14.3 32 32 32h176v32H160c-26.5 0-48 21.5-48 48v96c0 26.5 21.5 48 48 48h128c26.5 0 48-21.5 48-48V368c0-26.5-21.5-48-48-48H240V288H400v32H352c-26.5 0-48 21.5-48 48v96c0 26.5 21.5 48 48 48h128c26.5 0 48-21.5 48-48V368c0-26.5-21.5-48-48-48H432V288H608c17.7 0 32-14.3 32-32s-14.3-32-32-32H352V192h48c26.5 0 48-21.5 48-48V48c0-26.5-21.5-48-48-48H240zM192 400H288v64H192V400zm256 0H544v64H448V400z"/></svg> Network & Infrastructure Oversight</div><div class="feature-card-desc">Firewall configuration reviews, DNS management, VLAN segmentation oversight, and network performance monitoring.</div></div>
@@ -380,9 +383,12 @@
<div class="section-title">User Package</div>
<div class="section-subtitle">Per-user monthly services — identity, email, security &amp; helpdesk</div>
<span class="section-badge">Per User / Month</span>
<div class="sec-collapsed-counter">
<button class="sec-count-btn" onclick="stepCount('userCount',-1,event)">&minus;</button>
<button class="sec-count-btn" onclick="stepCount('userCount',1,event)">+</button>
<div class="sec-collapsed-counter" onclick="event.stopPropagation()">
<div class="num-stepper">
<button class="step-btn" onclick="stepInput('userCount',-1)">&minus;</button>
<input class="num-input" id="userCount" type="number" min="0" value="1" oninput="update()">
<button class="step-btn" onclick="stepInput('userCount',1)">+</button>
</div>
</div>
</div>
<span id="sec02-summary" class="sec-summary-badge"></span>
@@ -414,14 +420,14 @@
<!-- What's Included collapsible -->
<div class="collapsible-header" onclick="toggleCollapsible('userIncluded')" tabindex="0" role="button" onkeydown="if(event.key==='Enter'||event.key===' '){toggleCollapsible('userIncluded');event.preventDefault();}">
<span class="collapsible-toggle" id="userIncluded-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></span>
<span class="collapsible-toggle open" id="userIncluded-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></span>
<span class="collapsible-label">What's Included in This Package</span>
</div>
<div class="collapsible-body" id="userIncluded">
<div class="collapsible-body open" id="userIncluded">
<ul class="feature-list">
<li>Microsoft 365 Business Premium (M365 tier) — Word, Excel, PowerPoint, Teams, Exchange</li>
<li>Entra ID &amp; MFA — identity protection, conditional access, and SSO</li>
<li>Microsoft Defender for Business — endpoint + email threat protection</li>
<li class="m365-feature">Microsoft 365 Business Premium (M365 tier) — Word, Excel, PowerPoint, Teams, Exchange</li>
<li class="m365-feature">Entra ID &amp; MFA — identity protection, conditional access, and SSO</li>
<li class="m365-feature">Microsoft Defender for Business — endpoint + email threat protection</li>
<li>Helpdesk support (business hours) — tickets, remote sessions, escalations</li>
<li>Onboarding &amp; offboarding — provisioning, access revocation, equipment checklists</li>
<li>Security awareness training (SAT) — phishing simulations &amp; training modules</li>
@@ -429,29 +435,19 @@
</ul>
</div>
<div class="input-row">
<div>
<div class="input-label">Number of Users</div>
</div>
<div class="num-stepper">
<button class="step-btn" onclick="stepInput('userCount',-1)">&minus;</button>
<input class="num-input" id="userCount" type="number" min="0" value="1" oninput="update()">
<button class="step-btn" onclick="stepInput('userCount',1)">+</button>
</div>
</div>
<!-- Per-User Add-Ons collapsible -->
<div class="collapsible-header collapsible-header--addon" onclick="toggleCollapsible('addonsA')" tabindex="0" role="button" onkeydown="if(event.key==='Enter'||event.key===' '){toggleCollapsible('addonsA');event.preventDefault();}">
<span class="collapsible-toggle" id="addonsA-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></span>
<span class="collapsible-toggle open" id="addonsA-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></span>
<span class="collapsible-label">Per-User Add-Ons</span>
<div id="addonsA-preview" class="addon-preview-wrap">
<span class="addon-preview-pill">Extended Hours</span>
<span class="addon-preview-pill">1Password</span>
<span class="addon-preview-pill">INKY Pro</span>
<span class="addon-preview-pill">Zero Trust</span>
<div id="addonsA-preview" class="addon-preview-wrap" style="display:none">
<span class="addon-preview-pill" data-addon="addExtHours">Extended Hours</span>
<span class="addon-preview-pill" data-addon="addPWM">1Password</span>
<span class="addon-preview-pill" data-addon="addINKY">INKY Pro</span>
<span class="addon-preview-pill" data-addon="addZT">Zero Trust</span>
</div>
</div>
<div class="collapsible-body" id="addonsA">
<div class="collapsible-body open" id="addonsA">
<div class="addon-grid">
<label class="addon-row" id="row-ext" onclick="toggleAddon('addExtHours','row-ext');update()">
<input type="checkbox" id="addExtHours">
@@ -494,9 +490,12 @@
<div class="section-title">Endpoint Package</div>
<div class="section-subtitle">Per-device managed protection — workstations &amp; laptops</div>
<span class="section-badge">$35 / Endpoint / Month</span>
<div class="sec-collapsed-counter">
<button class="sec-count-btn" onclick="stepCount('endpointCount',-1,event)">&minus;</button>
<button class="sec-count-btn" onclick="stepCount('endpointCount',1,event)">+</button>
<div class="sec-collapsed-counter" onclick="event.stopPropagation()">
<div class="num-stepper">
<button class="step-btn" onclick="stepInput('endpointCount',-1)">&minus;</button>
<input class="num-input" id="endpointCount" type="number" min="0" value="1" oninput="update()">
<button class="step-btn" onclick="stepInput('endpointCount',1)">+</button>
</div>
</div>
</div>
<span id="sec03-summary" class="sec-summary-badge"></span>
@@ -507,10 +506,10 @@
<!-- What's Included collapsible -->
<div class="collapsible-header" onclick="toggleCollapsible('endpointIncluded')" tabindex="0" role="button" onkeydown="if(event.key==='Enter'||event.key===' '){toggleCollapsible('endpointIncluded');event.preventDefault();}">
<span class="collapsible-toggle" id="endpointIncluded-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></span>
<span class="collapsible-toggle open" id="endpointIncluded-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></span>
<span class="collapsible-label">What's Included in This Package</span>
</div>
<div class="collapsible-body" id="endpointIncluded">
<div class="collapsible-body open" id="endpointIncluded">
<ul class="feature-list">
<li>RMM agent — remote monitoring, patching &amp; automated remediation</li>
<li>Huntress EDR — 24/7 SOC-backed threat hunting &amp; incident response</li>
@@ -521,28 +520,17 @@
</ul>
</div>
<div class="input-row">
<div>
<div class="input-label">Number of Endpoints</div>
<div class="input-sublabel">Workstations, laptops — per managed device</div>
</div>
<div class="num-stepper">
<button class="step-btn" onclick="stepInput('endpointCount',-1)">&minus;</button>
<input class="num-input" id="endpointCount" type="number" min="0" value="1" oninput="update()">
<button class="step-btn" onclick="stepInput('endpointCount',1)">+</button>
</div>
</div>
<!-- Per-Endpoint Add-Ons collapsible -->
<div class="collapsible-header collapsible-header--addon" onclick="toggleCollapsible('addonsB')" tabindex="0" role="button" onkeydown="if(event.key==='Enter'||event.key===' '){toggleCollapsible('addonsB');event.preventDefault();}">
<span class="collapsible-toggle" id="addonsB-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></span>
<span class="collapsible-toggle open" id="addonsB-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></span>
<span class="collapsible-label">Per-Endpoint Add-Ons</span>
<div id="addonsB-preview" class="addon-preview-wrap">
<span class="addon-preview-pill">Bare Metal Backup</span>
<span class="addon-preview-pill">USB Blocking</span>
<div id="addonsB-preview" class="addon-preview-wrap" style="display:none">
<span class="addon-preview-pill" data-addon="addBMB">Bare Metal Backup</span>
<span class="addon-preview-pill" data-addon="addUSB">USB Blocking</span>
</div>
</div>
<div class="collapsible-body" id="addonsB">
<div class="collapsible-body open" id="addonsB">
<div class="addon-grid">
<label class="addon-row" id="row-bmb" onclick="toggleAddon('addBMB','row-bmb');update()">
<input type="checkbox" id="addBMB">
@@ -576,9 +564,12 @@
<div class="section-title">Server Management</div>
<div class="section-subtitle">Dedicated management for physical &amp; virtual servers</div>
<span class="section-badge">$120 / Server / Month</span>
<div class="sec-collapsed-counter">
<button class="sec-count-btn" onclick="stepCount('serverCount',-1,event)">&minus;</button>
<button class="sec-count-btn" onclick="stepCount('serverCount',1,event)">+</button>
<div class="sec-collapsed-counter" onclick="event.stopPropagation()">
<div class="num-stepper">
<button class="step-btn" onclick="stepInput('serverCount',-1)">&minus;</button>
<input class="num-input" id="serverCount" type="number" min="0" value="0" oninput="update()">
<button class="step-btn" onclick="stepInput('serverCount',1)">+</button>
</div>
</div>
</div>
<span id="sec04-summary" class="sec-summary-badge"></span>
@@ -602,17 +593,6 @@
</ul>
</div>
<div class="input-row">
<div>
<div class="input-label">Number of Servers</div>
<div class="input-sublabel">Physical or virtual — each managed server</div>
</div>
<div class="num-stepper">
<button class="step-btn" onclick="stepInput('serverCount',-1)">&minus;</button>
<input class="num-input" id="serverCount" type="number" min="0" value="0" oninput="update()">
<button class="step-btn" onclick="stepInput('serverCount',1)">+</button>
</div>
</div>
</div>
</div>
@@ -635,9 +615,12 @@
<div class="section-title">Zero Trust Networking <span class="section-title-tag">HaaS</span></div>
<div class="section-subtitle">Cytracom-powered ZT network access — seats &amp; managed hardware as a service</div>
<span class="section-badge">Per User + Per Device / Month</span>
<div class="sec-collapsed-counter">
<button class="sec-count-btn" onclick="stepCount('ztNetSeats',-1,event)">&minus;</button>
<button class="sec-count-btn" onclick="stepCount('ztNetSeats',1,event)">+</button>
<div class="sec-collapsed-counter" onclick="event.stopPropagation()">
<div class="num-stepper">
<button class="step-btn" onclick="stepInput('ztNetSeats',-1)">&minus;</button>
<input class="num-input" id="ztNetSeats" type="number" min="0" value="0" oninput="update()">
<button class="step-btn" onclick="stepInput('ztNetSeats',1)">+</button>
</div>
</div>
</div>
<span id="sec05-summary" class="sec-summary-badge"></span>
@@ -654,17 +637,6 @@
Section V adds <em>ZT network infrastructure</em>: <strong>seats</strong> cover non-user devices (printers, IoT, cameras) that need network access control, and <strong>routers</strong> are the managed ZTNA gateway hardware delivered as a service. Both can be active together.</span>
</div>
<div class="input-row">
<div>
<div class="input-label">5A — ZT User Seats</div>
<div class="input-sublabel">Identity-aware access control per user · $25/seat/mo</div>
</div>
<div class="num-stepper">
<button class="step-btn" onclick="stepInput('ztNetSeats',-1)">&minus;</button>
<input class="num-input" id="ztNetSeats" type="number" min="0" value="0" oninput="update()">
<button class="step-btn" onclick="stepInput('ztNetSeats',1)">+</button>
</div>
</div>
<div class="input-row">
<div>
<div class="input-label">5B — HaaS Devices</div>
@@ -688,7 +660,7 @@
.tier-seg.active set by activateTier() AND update()
#voipSeats — seat count input
#addVoipPhone — Desk Phone HaaS +$15/seat
#addVoipFax — eFax line +$10 flat (not per seat)
#addVoipFax — eFax line +$10/seat/mo
#currentPhoneBill — optional savings comparator input
#savingsComparator — green/amber result, rendered by updateSavings()
voipTotal NOT counted in baseSubtotal (no effect on admin fee)
@@ -701,6 +673,13 @@
<div class="section-title">VoIP / Unified Communications <span class="section-title-tag">UCaaS</span></div>
<div class="section-subtitle">United Cloud-powered business phone — seats, features &amp; optional desk phones</div>
<span class="section-badge">Per Seat / Month</span>
<div class="sec-collapsed-counter" onclick="event.stopPropagation()">
<div class="num-stepper">
<button class="step-btn" onclick="stepInput('voipSeats',-1)">&minus;</button>
<input class="num-input" id="voipSeats" type="number" min="0" value="0" oninput="update()">
<button class="step-btn" onclick="stepInput('voipSeats',1)">+</button>
</div>
</div>
</div>
<span id="sec06-summary" class="sec-summary-badge"></span>
<div class="sec-chevron"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg></div>
@@ -730,17 +709,6 @@
</label>
</div>
<div class="input-row">
<div>
<div class="input-label">Number of Seats</div>
<div class="input-sublabel">One seat per phone user</div>
</div>
<div class="num-stepper">
<button class="step-btn" onclick="stepInput('voipSeats',-1)">&minus;</button>
<input class="num-input" id="voipSeats" type="number" min="0" value="0" oninput="update()">
<button class="step-btn" onclick="stepInput('voipSeats',1)">+</button>
</div>
</div>
<div class="addon-grid" style="margin-top:8px;">
<label class="addon-row" id="row-vphone" onclick="toggleAddon('addVoipPhone','row-vphone');update()">
@@ -783,6 +751,22 @@
<div class="sidebar-title">SVS MSP — Live Quote</div>
<div class="sidebar-client" id="clientNameDisplay">Client Name</div>
</div>
<!-- ── INSIGHT NUDGE BANNER ──────────────────────────────────────
Sits flush under .sidebar-header, full width of .sidebar.
Controlled entirely by renderNudge() via applyNudge("").
Mobile duplicate: #nudgeBanner_m (synced via syncClass/syncEl).
.hidden class toggled by renderNudge() when nudges=[]
──────────────────────────────────────────────────────────────── -->
<div id="nudgeBanner" class="nudge-banner amber hidden">
<div class="nudge-header-row">
<span class="nudge-banner-label" style="margin-bottom:0;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" width="12" height="14" fill="currentColor" style="margin-right:6px;vertical-align:middle;"><path d="M272 384c9.6-31.9 29.5-59.1 49.2-86.2l0 0c5.2-7.1 10.4-14.2 15.4-21.4c19.8-28.5 31.4-63 31.4-100.3C368 78.8 289.2 0 192 0S16 78.8 16 176c0 37.3 11.6 71.9 31.4 100.3c5 7.2 10.2 14.3 15.4 21.4l0 0c19.8 27.1 39.7 54.4 49.2 86.2H272zM192 512c44.2 0 80-35.8 80-80V416H112v16c0 44.2 35.8 80 80 80zM112 352H272c0 0 0 0 0 0H112c0 0 0 0 0 0z"/></svg> Insight <span id="nudgeCounter" class="nudge-counter"></span></span>
<div class="nudge-nav-group">
<button onclick="cycleNudge(-1)" class="nudge-nav-btn" title="Previous"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg></button>
<button onclick="cycleNudge(1)" class="nudge-nav-btn" title="Next"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg></button>
</div>
</div>
<span id="nudgeText"></span>
</div>
<div class="sidebar-body">
<!-- ── SIDEBAR SERVICE LINES ──────────────────────────────────
Each .sidebar-line hidden by default.
@@ -800,12 +784,12 @@
<span><span class="lbl-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" width="14" height="13" fill="currentColor" style="vertical-align:middle;"><path d="M144 0a80 80 0 1 1 0 160A80 80 0 1 1 144 0zM512 0a80 80 0 1 1 0 160A80 80 0 1 1 512 0zM0 298.7C0 239.8 47.8 192 106.7 192h42.7c15.9 0 31 3.5 44.6 9.7c-1.3 7.2-1.9 14.7-1.9 22.3c0 38.2 16.8 72.5 43.3 96c-.2 0-.4 0-.7 0H21.3C9.6 320 0 310.4 0 298.7zM405.3 320c-.2 0-.4 0-.7 0c26.6-23.5 43.3-57.8 43.3-96c0-7.6-.7-15-1.9-22.3c13.6-6.3 28.7-9.7 44.6-9.7h42.7C592.2 192 640 239.8 640 298.7c0 11.8-9.6 21.3-21.3 21.3H405.3zM224 224a96 96 0 1 1 192 0 96 96 0 1 1 -192 0zM128 485.3C128 411.7 187.7 352 261.3 352H378.7C452.3 352 512 411.7 512 485.3c0 14.7-11.9 26.7-26.7 26.7H154.7c-14.7 0-26.7-11.9-26.7-26.7z"/></svg></span> Users</span>
<span class="val" id="sl-users-val"></span>
</div>
<div class="sl-sub" style="display:none;" id="sl-users-sub"></div>
<div class="sl-sub hidden" id="sl-users-sub"></div>
<div class="sidebar-line hidden" id="sl-endpoints">
<span><span class="lbl-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" width="14" height="13" fill="currentColor" style="vertical-align:middle;"><path d="M64 0C28.7 0 0 28.7 0 64V352c0 35.3 28.7 64 64 64H240l-10.7 32H160c-17.7 0-32 14.3-32 32s14.3 32 32 32H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H346.7L336 416H512c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64H64zM512 64V352H64V64H512z"/></svg></span> Endpoints</span>
<span class="val" id="sl-endpoints-val"></span>
</div>
<div class="sl-sub" style="display:none;" id="sl-endpoints-sub"></div>
<div class="sl-sub hidden" id="sl-endpoints-sub"></div>
<div class="sidebar-line hidden" id="sl-servers">
<span><span class="lbl-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="13" height="13" fill="currentColor" style="vertical-align:middle;"><path d="M64 32C28.7 32 0 60.7 0 96v64c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zm280 72a24 24 0 1 1 0 48 24 24 0 1 1 0-48zm48 24a24 24 0 1 1 48 0 24 24 0 1 1 -48 0zM64 288c-35.3 0-64 28.7-64 64v64c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V352c0-35.3-28.7-64-64-64H64zm280 72a24 24 0 1 1 0 48 24 24 0 1 1 0-48zm56 24a24 24 0 1 1 48 0 24 24 0 1 1 -48 0z"/></svg></span> Servers</span>
<span class="val" id="sl-servers-val"></span>
@@ -864,7 +848,7 @@
<span>Annual Projection</span>
<span class="val" id="annualDisplay">$1,800</span>
</div>
<div class="sidebar-line" id="perUserRow" style="display:none;">
<div class="sidebar-line hidden" id="perUserRow">
<span>Avg. Cost Per User<br><small id="perUserBreakdown" class="per-user-cost-sub sidebar-note-mono hidden"></small></span>
<span class="val" id="perUserDisplay"></span>
</div>
@@ -899,25 +883,6 @@
<div class="vs-footnote" id="vs-footnote"></div>
</div>
<!-- ── INSIGHT NUDGE BANNER ────────────────────────────────────
MUST remain inside .sidebar-body div.
If this div is placed after the closing sidebar-body div
it breaks on mobile (clipped outside container).
Controlled entirely by renderNudge() via applyNudge("").
Mobile duplicate: #nudgeBanner_m (synced via syncClass/syncEl).
.hidden class toggled by renderNudge() when nudges=[]
──────────────────────────────────────────────────────────────── -->
<div id="nudgeBanner" class="nudge-banner amber hidden">
<div class="nudge-header-row">
<span class="nudge-banner-label" style="margin-bottom:0;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" width="12" height="14" fill="currentColor" style="margin-right:6px;vertical-align:middle;"><path d="M272 384c9.6-31.9 29.5-59.1 49.2-86.2l0 0c5.2-7.1 10.4-14.2 15.4-21.4c19.8-28.5 31.4-63 31.4-100.3C368 78.8 289.2 0 192 0S16 78.8 16 176c0 37.3 11.6 71.9 31.4 100.3c5 7.2 10.2 14.3 15.4 21.4l0 0c19.8 27.1 39.7 54.4 49.2 86.2H272zM192 512c44.2 0 80-35.8 80-80V416H112v16c0 44.2 35.8 80 80 80zM112 352H272c0 0 0 0 0 0H112c0 0 0 0 0 0z"/></svg> Insight <span id="nudgeCounter" class="nudge-counter"></span></span>
<div class="nudge-nav-group">
<button onclick="cycleNudge(-1)" class="nudge-nav-btn" title="Previous"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg></button>
<button onclick="cycleNudge(1)" class="nudge-nav-btn" title="Next"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg></button>
</div>
</div>
<span id="nudgeText"></span>
</div>
<!-- ── EXPORT BUTTONS ───────────────────────────────────────────
Export A: window.print() — triggers browser print/save-as-PDF.
Export B: exportQuoteJSON() — downloads .json + copies to clipboard.