GPT is about to go nuts with my project.

This commit is contained in:
2026-03-13 17:21:38 -04:00
parent 591e4155fc
commit bce93507cb
4 changed files with 712 additions and 255 deletions

View File

@@ -37,10 +37,10 @@
.mobile-panel-backdrop — dark blur overlay, click to close
.mobile-panel-sheet — slides up from bottom, max-height:92vh
.mobile-panel-handle — decorative drag indicator bar
.mobile-panel-close-row — "QUOTE SUMMARY" label + × button
#mobilePanelContent — contains duplicate sidebar (_m IDs)
DUPLICATE SIDEBAR: All sidebar IDs duplicated with _m suffix.
update() calls syncEl/syncClass/syncStyle to keep _m in sync.
.mobile-panel-close-row — "QUOTE SUMMARY" label + × button
#mobilePanelContent — receives a JS-cloned sidebar with _m IDs
MOBILE SIDEBAR: Built from the desktop sidebar on boot.
update() syncs the cloned _m elements after each render.
Never DOM-move the real .sidebar here — it breaks desktop.
════════════════════════════════════════════════════════════ -->
<div class="mobile-quote-panel" id="mobileQuotePanel">
@@ -54,144 +54,11 @@
<div class="mobile-panel-actions">
<button type="button" class="btn-reset-quote" onclick="openResetConfirm()">Reset Quote</button>
</div>
<!-- Sidebar content injected here by JS on first open -->
<div id="mobilePanelContent">
<div class="sidebar">
<div class="sidebar-header">
<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>
<div class="sidebar-note hidden" id="sideNote-byol_m"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="14" height="14" fill="var(--amber)" class="note-icon"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24V264c0 13.3-10.7 24-24 24s-24-10.7-24-24V152c0-13.3 10.7-24 24-24zm-32 224a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg> BYOL selected — client handles their own Microsoft or Google licensing</div>
<div class="sidebar-line hidden" id="sl-users_m">
<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 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 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>
</div>
<div class="sidebar-line hidden" id="sl-zt_m">
<span><span class="lbl-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="11" height="13" fill="currentColor" style="vertical-align:middle;"><path d="M144 144v48H304V144c0-44.2-35.8-80-80-80s-80 35.8-80 80zM80 192V144C80 64.5 144.5 0 224 0s144 64.5 144 144v48h16c35.3 0 64 28.7 64 64V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V256c0-35.3 28.7-64 64-64H80z"/></svg></span> Zero Trust</span>
<span class="val" id="sl-zt-val_m"></span>
</div>
<div class="sidebar-line hidden" id="sl-voip_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="M164.9 24.6c-7.7-18.6-28-28.5-47.4-23.2l-88 24C11.7 30.3 0 46.7 0 64C0 311.4 200.6 512 448 512c17.3 0 33.7-11.7 38.6-29.5l24-88c5.3-19.4-4.6-39.7-23.2-47.4l-96-40c-16.3-6.8-35.2-2.1-46.3 11.6L304.7 368C234.3 334.7 177.3 277.7 144 207.3L193.3 167c13.7-11.2 18.4-30 11.6-46.3l-40-96z"/></svg></span> VoIP</span>
<span class="val" id="sl-voip-val_m"></span>
</div>
<div class="sidebar-line" id="sl-admin_m">
<span><span class="lbl-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" width="12" height="13" fill="currentColor" style="vertical-align:middle;"><path d="M48 0C21.5 0 0 21.5 0 48V464c0 26.5 21.5 48 48 48h96V432c0-26.5 21.5-48 48-48s48 21.5 48 48v80h96c26.5 0 48-21.5 48-48V48c0-26.5-21.5-48-48-48H48zM64 240c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V240zm112-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H176c-8.8 0-16-7.2-16-16V240c0-8.8 7.2-16 16-16zm48-80v32c0 8.8-7.2 16-16 16H176c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16zm-144-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V144c0-8.8 7.2-16 16-16zm144 208h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H272c-8.8 0-16-7.2-16-16V352c0-8.8 7.2-16 16-16zm-144-16h32c8.8 0 16 7.2 16 16v32c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V352c0-8.8 7.2-16 16-16z"/></svg></span> Site Admin Fee</span>
<span class="val" id="sl-admin-val_m">$150</span>
</div>
<div class="sl-sub hidden" id="sl-admin-sub_m"></div>
</div>
<!-- Discount line — hidden when no term discount -->
<div class="sidebar-line sidebar-line-discount hidden" id="sl-base-mrr-row_m">
<span class="sl-muted">Base MRR</span>
<span class="val sl-muted" id="sl-base-mrr-val_m"></span>
</div>
<div class="sidebar-line sidebar-line-discount hidden" id="sl-discount-row_m">
<span class="sl-muted">Term Discount</span>
<span class="val sl-discount-val" id="sl-discount-val_m"></span>
</div>
<div class="sidebar-mrr-label">Monthly Recurring (MRR)</div>
<div class="sidebar-mrr" id="mrrDisplay_m">$150</div>
<label class="sl-hst-toggle">
<input type="checkbox" id="hstToggle_m" onchange="document.getElementById('hstToggle').checked=this.checked; update();">
<span>Include Ontario HST (13%)</span>
</label>
<!-- HST line -->
<div class="sidebar-line sidebar-line-hst hidden" id="sl-hst-row_m">
<span class="sl-muted">HST (13%)</span>
<span class="val sl-hst-val" id="sl-hst-val_m"></span>
</div>
<!-- Total inc. HST -->
<div class="sidebar-line sidebar-line-total hidden" id="sl-hst-total-row_m">
<span>Total (inc. HST)</span>
<span class="val" id="sl-hst-total-val_m"></span>
</div>
<!-- Onboarding fee line -->
<div class="sidebar-line hidden" id="sl-otf-row_m">
<span>Onboarding Fee</span>
<span class="val" id="sl-otf-val_m"></span>
</div>
<div class="sidebar-line">
<span>Annual Projection</span>
<span class="val" id="annualDisplay_m">$1,800</span>
</div>
<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>
<!-- VS Hiring In-House -->
<div id="vsComparison_m" class="hidden vs-comparison-wrap">
<div class="vs-label">VS. Hiring In-House</div>
<table class="vs-table">
<tr>
<td>
<svg width="14" height="14" viewBox="0 0 72 98" class="vs-inline-icon" xmlns="http://www.w3.org/2000/svg">
<polyline points="7.32 8.88 62.11 8.88 34.72 58.22" fill="#1f75a6"/>
<polyline points="40.7 55.33 64.4 12.64 71.88 12.64 44.48 61.99 40.7 55.33" fill="#8d252f"/>
</svg>
<span class="vs-svs-label">SVS MSP</span>
</td>
<td class="vs-val-accent" id="vs-svs-annual_m"></td>
</tr>
<tr><td class="vs-td-muted"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="12" height="13" fill="currentColor" class="vs-td-icon"><path d="M224 256A128 128 0 1 0 224 0a128 128 0 1 0 0 256zm-45.7 48C79.8 304 0 383.8 0 482.3C0 498.7 13.3 512 29.7 512H418.3c16.4 0 29.7-13.3 29.7-29.7C448 383.8 368.2 304 269.7 304H178.3z"/></svg> 1 IT person + tools</td><td class="vs-td-muted" id="vs-1man-cost_m"></td></tr>
<tr class="vs-save-row" id="vs-1man-save-row_m"><td><span id="vs-1man-save-lbl_m" class="vs-val-green">YOU SAVE</span></td><td id="vs-1man-save_m" class="vs-val-green"></td></tr>
<tr><td class="vs-td-muted"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" width="14" height="13" fill="currentColor" class="vs-td-icon"><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> 5-person team</td><td class="vs-td-muted" id="vs-5man-cost_m"></td></tr>
<tr class="vs-save-row" id="vs-5man-save-row_m"><td><span id="vs-5man-save-lbl_m" class="vs-val-green">YOU SAVE</span></td><td id="vs-5man-save_m" class="vs-val-green"></td></tr>
</table>
<div class="vs-footnote" id="vs-footnote_m"></div>
</div>
</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>
Print / Save PDF
</button>
<button class="btn-export btn-export-secondary" onclick="exportQuoteJSON()">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" width="12" height="14" fill="currentColor" style="margin-right:7px;vertical-align:middle;"><path d="M64 0C28.7 0 0 28.7 0 64V448c0 35.3 28.7 64 64 64H320c35.3 0 64-28.7 64-64V160H256c-17.7 0-32-14.3-32-32V0H64zM256 0V128h128L256 0zM216 232l-96 96 96 96 22.6-22.6L169.3 328l69.3-69.4L216 232zM168 232l-22.6 22.6 69.3 69.4-69.3 69.4L168 416l96-96-96-96z"/></svg>
Export JSON + Copy
</button>
</div>
</div>
</div>
<!-- Sidebar content injected by JS from the desktop sidebar markup -->
<div id="mobilePanelContent"></div>
</div>
</div>
<!-- TOP BAR -->
<!-- ── TOP BAR ── sticky, z-index:100, cream bg (#ddd8d0) ──────── -->
<header class="top-bar">
@@ -754,7 +621,7 @@
<!-- SIDEBAR -->
<!-- ── RIGHT COLUMN: sticky sidebar (desktop only ≥1100px) ─────────
Hidden on mobile via CSS. Duplicate exists in #mobilePanelContent.
Hidden on mobile via CSS. Mobile clone is injected into #mobilePanelContent.
nudgeBanner must stay INSIDE .sidebar-body or it gets clipped.
──────────────────────────────────────────────────────────────── -->
<div class="side-col">
@@ -769,7 +636,7 @@
<!-- ── 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).
Mobile clone target: #nudgeBanner_m (synced via syncClass/syncEl).
.hidden class toggled by renderNudge() when nudges=[]
──────────────────────────────────────────────────────────────── -->
<div id="nudgeBanner" class="nudge-banner amber hidden">
@@ -976,3 +843,4 @@
<script src="SVS-MSP-Calculator.js"></script>
</body>
</html>