GPT is about to go nuts with my project.
This commit is contained in:
502
SVS-MSP-Calculator-glass.css
Normal file
502
SVS-MSP-Calculator-glass.css
Normal file
@@ -0,0 +1,502 @@
|
||||
/* ══════════════════════════════════════════════════════════════
|
||||
SVS MSP Calculator — Glass Dark Theme
|
||||
Imported dynamically by the theme toggle as a third test theme.
|
||||
Keeps the existing HTML structure intact and overrides presentation only.
|
||||
══════════════════════════════════════════════════════════════ */
|
||||
|
||||
html {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
:root {
|
||||
--ink: #eef6ff;
|
||||
--paper: #08111c;
|
||||
--accent: #69c8ff;
|
||||
--muted: #9fb3c9;
|
||||
--border: rgba(143, 183, 221, 0.2);
|
||||
--card: rgba(10, 18, 31, 0.62);
|
||||
--green: #63d8a2;
|
||||
--amber: #ffbe68;
|
||||
--glass-header-text: #5f6d7f;
|
||||
}
|
||||
|
||||
body {
|
||||
background:
|
||||
linear-gradient(138deg, #03070f 0%, #071120 14%, #10152a 30%, #1a1324 48%, #1a0d17 64%, #0a111b 82%, #050912 100%),
|
||||
linear-gradient(125deg, rgba(44, 138, 255, 0.22) 0%, rgba(44, 138, 255, 0) 26%),
|
||||
linear-gradient(142deg, rgba(18, 194, 152, 0.16) 18%, rgba(18, 194, 152, 0) 42%),
|
||||
linear-gradient(154deg, rgba(118, 72, 224, 0.16) 34%, rgba(118, 72, 224, 0) 58%),
|
||||
linear-gradient(164deg, rgba(198, 46, 86, 0.16) 52%, rgba(198, 46, 86, 0) 74%),
|
||||
radial-gradient(circle at 8% 10%, rgba(72, 178, 255, 0.34), transparent 26%),
|
||||
radial-gradient(circle at 26% 30%, rgba(32, 196, 144, 0.24), transparent 22%),
|
||||
radial-gradient(circle at 58% 18%, rgba(116, 82, 222, 0.24), transparent 22%),
|
||||
radial-gradient(circle at 88% 16%, rgba(200, 60, 92, 0.24), transparent 20%),
|
||||
radial-gradient(circle at 76% 56%, rgba(42, 126, 255, 0.2), transparent 22%),
|
||||
radial-gradient(circle at 24% 78%, rgba(112, 44, 138, 0.22), transparent 22%),
|
||||
radial-gradient(circle at 92% 86%, rgba(18, 168, 132, 0.2), transparent 20%),
|
||||
linear-gradient(160deg, rgba(7, 14, 25, 0.72) 0%, rgba(5, 10, 19, 0.8) 100%);
|
||||
background-attachment: fixed;
|
||||
color: var(--ink);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background:
|
||||
linear-gradient(132deg, rgba(58, 154, 255, 0.16) 0%, rgba(58, 154, 255, 0) 34%),
|
||||
linear-gradient(145deg, rgba(28, 201, 154, 0.12) 18%, rgba(28, 201, 154, 0) 44%),
|
||||
linear-gradient(156deg, rgba(122, 84, 232, 0.12) 38%, rgba(122, 84, 232, 0) 60%),
|
||||
linear-gradient(168deg, rgba(214, 68, 104, 0.12) 58%, rgba(214, 68, 104, 0) 78%);
|
||||
opacity: 0.9;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.outer,
|
||||
.pitch-wrap {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: rgba(105, 200, 255, 0.28);
|
||||
color: #f8fbff;
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
position: sticky !important;
|
||||
top: 0 !important;
|
||||
z-index: 100 !important;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(252, 255, 255, 0.96) 0%,
|
||||
rgba(244, 249, 255, 0.93) 52%,
|
||||
rgba(231, 240, 251, 0.91) 100%
|
||||
) !important;
|
||||
border-bottom: 1px solid rgba(118, 143, 171, 0.35) !important;
|
||||
box-shadow: 0 8px 24px rgba(7, 18, 33, 0.1) !important;
|
||||
backdrop-filter: blur(18px) saturate(135%);
|
||||
-webkit-backdrop-filter: blur(18px) saturate(135%);
|
||||
}
|
||||
|
||||
.top-bar-right {
|
||||
color: var(--glass-header-text) !important;
|
||||
}
|
||||
|
||||
@media (min-width: 1101px) {
|
||||
.outer {
|
||||
padding-top: var(--sidebar-top-gap) !important;
|
||||
}
|
||||
|
||||
.side-col {
|
||||
top: var(--sidebar-sticky-top) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-toggle-btn {
|
||||
background: linear-gradient(180deg, rgba(247, 250, 255, 0.88), rgba(217, 229, 242, 0.82)) !important;
|
||||
border: 1px solid rgba(83, 117, 150, 0.24) !important;
|
||||
color: #223142 !important;
|
||||
box-shadow: 0 10px 24px rgba(6, 18, 31, 0.14) !important;
|
||||
}
|
||||
|
||||
.theme-toggle-btn:hover {
|
||||
background: linear-gradient(180deg, rgba(252, 254, 255, 0.94), rgba(226, 237, 248, 0.88)) !important;
|
||||
}
|
||||
|
||||
.theme-toggle-btn:active {
|
||||
background: linear-gradient(180deg, rgba(226, 236, 248, 0.95), rgba(205, 219, 235, 0.9)) !important;
|
||||
}
|
||||
|
||||
.section,
|
||||
.quote-settings-bar,
|
||||
.sidebar,
|
||||
.sidebar-utility .btn-reset-quote,
|
||||
.mobile-panel-sheet,
|
||||
.mobile-panel-close-row,
|
||||
.mobile-panel-actions,
|
||||
.confirm-modal-card,
|
||||
.feature-card,
|
||||
.addon-row,
|
||||
.vs-comparison-wrap,
|
||||
.export-wrap,
|
||||
.pitch-inner {
|
||||
background: linear-gradient(180deg, rgba(16, 27, 43, 0.76), rgba(9, 17, 29, 0.68)) !important;
|
||||
border-color: rgba(143, 183, 221, 0.18) !important;
|
||||
box-shadow:
|
||||
0 18px 50px rgba(2, 8, 17, 0.32),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.06) !important;
|
||||
backdrop-filter: blur(18px) saturate(135%);
|
||||
-webkit-backdrop-filter: blur(18px) saturate(135%);
|
||||
}
|
||||
|
||||
.section {
|
||||
background: linear-gradient(180deg, rgba(17, 29, 46, 0.74), rgba(9, 17, 29, 0.66)) !important;
|
||||
}
|
||||
|
||||
.section:hover {
|
||||
border-color: rgba(105, 200, 255, 0.34) !important;
|
||||
box-shadow:
|
||||
-4px 0 0 0 rgba(105, 200, 255, 0.36),
|
||||
0 20px 54px rgba(2, 8, 17, 0.38),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.07) !important;
|
||||
}
|
||||
|
||||
.sec-open {
|
||||
border-color: rgba(105, 200, 255, 0.5) !important;
|
||||
box-shadow:
|
||||
-4px 0 0 0 rgba(105, 200, 255, 0.5),
|
||||
0 22px 58px rgba(2, 8, 17, 0.42),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.08) !important;
|
||||
}
|
||||
|
||||
.section-num {
|
||||
color: rgba(226, 239, 255, 0.18) !important;
|
||||
text-shadow: 0 0 26px rgba(105, 200, 255, 0.1);
|
||||
}
|
||||
|
||||
.section-title,
|
||||
.confirm-modal-title,
|
||||
.sidebar-mrr,
|
||||
.sidebar-line .val,
|
||||
.vs-svs-label {
|
||||
color: #f4f9ff !important;
|
||||
}
|
||||
|
||||
.section-subtitle,
|
||||
.feature-card-desc,
|
||||
.sidebar-note,
|
||||
.sidebar-note-mono,
|
||||
.sl-sub,
|
||||
.vs-label,
|
||||
.vs-td-muted,
|
||||
.savings-prompt,
|
||||
.pitch-desc,
|
||||
.qs-label,
|
||||
.qs-toggle-label,
|
||||
.qs-fee-label,
|
||||
.qs-fee-dollar,
|
||||
.sidebar-line,
|
||||
.section-title-tag {
|
||||
color: var(--muted) !important;
|
||||
}
|
||||
|
||||
.client-input {
|
||||
color: #f4f9ff !important;
|
||||
border-bottom-color: rgba(143, 183, 221, 0.24) !important;
|
||||
}
|
||||
|
||||
.client-input::placeholder {
|
||||
color: rgba(159, 179, 201, 0.72) !important;
|
||||
}
|
||||
|
||||
.sec-chevron,
|
||||
.addon-preview-pill,
|
||||
.btn-toggle-all,
|
||||
.confirm-btn-secondary,
|
||||
.btn-export-secondary,
|
||||
.mobile-panel-close,
|
||||
.nudge-nav-btn {
|
||||
background: rgba(255, 255, 255, 0.04) !important;
|
||||
border-color: rgba(143, 183, 221, 0.18) !important;
|
||||
color: var(--muted) !important;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
.sec-open .sec-chevron,
|
||||
.section-toggle:hover .sec-chevron,
|
||||
.btn-toggle-all:hover,
|
||||
.confirm-btn-secondary:hover,
|
||||
.btn-export-secondary:hover,
|
||||
.nudge-nav-btn:hover,
|
||||
.mobile-panel-close:hover {
|
||||
background: rgba(105, 200, 255, 0.12) !important;
|
||||
border-color: rgba(105, 200, 255, 0.3) !important;
|
||||
color: #f2f8ff !important;
|
||||
}
|
||||
|
||||
.pill-toggle,
|
||||
.tier-seg-wrap,
|
||||
.qs-fee-input-wrap {
|
||||
background: rgba(5, 11, 21, 0.3) !important;
|
||||
border-color: rgba(143, 183, 221, 0.18) !important;
|
||||
}
|
||||
|
||||
.pill-toggle label,
|
||||
.tier-seg {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.pill-toggle label:hover,
|
||||
.tier-seg:hover,
|
||||
.addon-row:hover {
|
||||
background: rgba(255, 255, 255, 0.04) !important;
|
||||
}
|
||||
|
||||
.tier-seg.active,
|
||||
.btn-export,
|
||||
.confirm-btn-danger,
|
||||
.mobile-quote-pill,
|
||||
.progress-fill {
|
||||
background: linear-gradient(135deg, #7ad6ff 0%, #4da8ff 52%, #337dff 100%) !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.pill-toggle input:checked + label {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0) 42%),
|
||||
linear-gradient(135deg, rgba(103, 182, 248, 0.7) 0%, rgba(63, 122, 210, 0.76) 58%, rgba(45, 82, 155, 0.82) 100%) !important;
|
||||
color: #ffffff !important;
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.12),
|
||||
inset 0 -1px 0 rgba(3, 10, 20, 0.2) !important;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0) 44%),
|
||||
linear-gradient(135deg, rgba(105, 188, 250, 0.82) 0%, rgba(71, 132, 224, 0.8) 54%, rgba(67, 72, 156, 0.76) 100%) !important;
|
||||
color: #ffffff !important;
|
||||
box-shadow:
|
||||
inset 0 -1px 0 rgba(255, 255, 255, 0.08),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.12) !important;
|
||||
}
|
||||
|
||||
.pill-toggle input:checked + label .pill-desc,
|
||||
.pill-toggle input:checked + label .pill-price,
|
||||
.tier-seg.active .tier-name,
|
||||
.tier-seg.active .tier-price,
|
||||
.tier-seg.active .tier-sub {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.num-input,
|
||||
.qs-fee-input,
|
||||
.qs-fee-dollar,
|
||||
.qs-fee-input-wrap,
|
||||
.mobile-panel-sheet .sidebar-body {
|
||||
background: rgba(5, 11, 21, 0.34) !important;
|
||||
}
|
||||
|
||||
.num-input,
|
||||
.qs-fee-input {
|
||||
border-color: rgba(143, 183, 221, 0.2) !important;
|
||||
color: #f2f8ff !important;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
.num-input:focus,
|
||||
.qs-fee-input:focus,
|
||||
.client-input:focus-visible {
|
||||
border-color: rgba(105, 200, 255, 0.55) !important;
|
||||
box-shadow: 0 0 0 3px rgba(105, 200, 255, 0.16) !important;
|
||||
}
|
||||
|
||||
.qs-switch {
|
||||
background: rgba(255, 255, 255, 0.12) !important;
|
||||
}
|
||||
|
||||
.qs-switch::after {
|
||||
background: rgba(250, 252, 255, 0.95) !important;
|
||||
box-shadow: 0 2px 8px rgba(3, 9, 18, 0.28);
|
||||
}
|
||||
|
||||
.addon-row.selected {
|
||||
background: rgba(105, 200, 255, 0.12) !important;
|
||||
border-color: rgba(105, 200, 255, 0.28) !important;
|
||||
}
|
||||
|
||||
.addon-row.selected .addon-name,
|
||||
.addon-row.selected .addon-price {
|
||||
color: #9edcff !important;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: linear-gradient(180deg, rgba(19, 31, 49, 0.8), rgba(10, 18, 30, 0.72)) !important;
|
||||
}
|
||||
|
||||
.callout-green,
|
||||
.nudge-banner.green,
|
||||
.admin-waive-savings {
|
||||
background: linear-gradient(180deg, rgba(15, 48, 42, 0.82), rgba(10, 35, 30, 0.72)) !important;
|
||||
border-color: rgba(99, 216, 162, 0.26) !important;
|
||||
color: var(--green) !important;
|
||||
}
|
||||
|
||||
.callout-red {
|
||||
background: linear-gradient(180deg, rgba(62, 23, 34, 0.82), rgba(41, 14, 22, 0.74)) !important;
|
||||
border-color: rgba(230, 117, 138, 0.26) !important;
|
||||
color: #ffb7c6 !important;
|
||||
}
|
||||
|
||||
.nudge-banner.amber,
|
||||
.admin-fee-waived-badge {
|
||||
background: linear-gradient(180deg, rgba(66, 41, 12, 0.84), rgba(43, 27, 8, 0.76)) !important;
|
||||
border-color: rgba(255, 190, 104, 0.26) !important;
|
||||
color: var(--amber) !important;
|
||||
}
|
||||
|
||||
.admin-waive-savings,
|
||||
.admin-fee-waived-badge,
|
||||
.addon-preview-pill.active {
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.addon-preview-pill.active {
|
||||
background: rgba(105, 200, 255, 0.16) !important;
|
||||
border-color: rgba(105, 200, 255, 0.28) !important;
|
||||
color: #dff3ff !important;
|
||||
}
|
||||
|
||||
.collapsible-header,
|
||||
.sidebar-line,
|
||||
.pitch-item,
|
||||
.vs-label::after,
|
||||
.sidebar-line-total,
|
||||
.pitch-footer,
|
||||
.export-wrap,
|
||||
.mobile-panel-close-row,
|
||||
.mobile-panel-actions {
|
||||
border-color: rgba(143, 183, 221, 0.14) !important;
|
||||
}
|
||||
|
||||
.sidebar-title,
|
||||
.sidebar-client.placeholder {
|
||||
color: rgba(255, 255, 255, 0.76) !important;
|
||||
}
|
||||
|
||||
.sidebar-body,
|
||||
.mobile-panel-sheet .sidebar,
|
||||
.mobile-panel-sheet .sidebar-body {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.sidebar-note strong,
|
||||
.sl-discount-val,
|
||||
.savings-amount {
|
||||
color: var(--green) !important;
|
||||
}
|
||||
|
||||
.sl-hst-toggle,
|
||||
.sl-hst-val,
|
||||
.sl-muted {
|
||||
color: var(--muted) !important;
|
||||
}
|
||||
|
||||
.vs-comparison-wrap {
|
||||
background: linear-gradient(180deg, rgba(14, 24, 39, 0.72), rgba(9, 16, 29, 0.62)) !important;
|
||||
}
|
||||
|
||||
.vs-save-green td {
|
||||
background: rgba(99, 216, 162, 0.14) !important;
|
||||
}
|
||||
|
||||
.vs-save-amber td {
|
||||
background: rgba(255, 190, 104, 0.14) !important;
|
||||
}
|
||||
|
||||
.export-wrap {
|
||||
border-top: 1px solid rgba(143, 183, 221, 0.14) !important;
|
||||
}
|
||||
|
||||
.btn-export {
|
||||
box-shadow: 0 14px 28px rgba(29, 108, 186, 0.26) !important;
|
||||
}
|
||||
|
||||
.btn-export:hover,
|
||||
.mobile-quote-pill:hover {
|
||||
filter: brightness(1.05);
|
||||
}
|
||||
|
||||
.btn-reset-quote {
|
||||
color: #dceefe !important;
|
||||
}
|
||||
|
||||
.btn-reset-quote:hover {
|
||||
background: rgba(105, 200, 255, 0.1) !important;
|
||||
border-color: rgba(105, 200, 255, 0.32) !important;
|
||||
color: #f2f8ff !important;
|
||||
}
|
||||
|
||||
.confirm-modal-backdrop {
|
||||
background: rgba(2, 7, 15, 0.72) !important;
|
||||
backdrop-filter: blur(10px) saturate(125%);
|
||||
-webkit-backdrop-filter: blur(10px) saturate(125%);
|
||||
}
|
||||
|
||||
.confirm-modal-card {
|
||||
background: linear-gradient(180deg, rgba(18, 29, 46, 0.86), rgba(10, 17, 29, 0.8)) !important;
|
||||
}
|
||||
|
||||
.pitch-inner {
|
||||
background: linear-gradient(180deg, rgba(14, 25, 40, 0.72), rgba(9, 16, 28, 0.68)) !important;
|
||||
}
|
||||
|
||||
.pitch-title {
|
||||
color: #f1f8ff !important;
|
||||
}
|
||||
|
||||
.pitch-footer {
|
||||
background: linear-gradient(135deg, rgba(11, 42, 34, 0.88), rgba(7, 28, 22, 0.86)) !important;
|
||||
color: #8ee8bf !important;
|
||||
}
|
||||
|
||||
.mobile-panel-sheet {
|
||||
background: linear-gradient(180deg, rgba(12, 21, 34, 0.92), rgba(8, 14, 24, 0.9)) !important;
|
||||
}
|
||||
|
||||
.mobile-panel-close-row,
|
||||
.mobile-panel-actions {
|
||||
background: rgba(10, 18, 30, 0.84) !important;
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.top-bar {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(251, 255, 255, 0.95) 0%,
|
||||
rgba(241, 247, 254, 0.91) 56%,
|
||||
rgba(228, 238, 250, 0.89) 100%
|
||||
) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
body {
|
||||
background:
|
||||
linear-gradient(150deg, #03070f 0%, #0a1220 24%, #171226 54%, #110d18 76%, #070c14 100%),
|
||||
linear-gradient(132deg, rgba(58, 154, 255, 0.16) 0%, rgba(58, 154, 255, 0) 34%),
|
||||
linear-gradient(150deg, rgba(28, 201, 154, 0.12) 18%, rgba(28, 201, 154, 0) 44%),
|
||||
linear-gradient(162deg, rgba(214, 68, 104, 0.11) 48%, rgba(214, 68, 104, 0) 72%),
|
||||
radial-gradient(circle at 16% 12%, rgba(72, 178, 255, 0.24), transparent 24%),
|
||||
radial-gradient(circle at 72% 20%, rgba(189, 58, 92, 0.18), transparent 20%),
|
||||
radial-gradient(circle at 34% 46%, rgba(32, 196, 144, 0.18), transparent 22%),
|
||||
radial-gradient(circle at 74% 60%, rgba(116, 82, 222, 0.16), transparent 20%);
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
body::before {
|
||||
background:
|
||||
linear-gradient(138deg, rgba(58, 154, 255, 0.14) 0%, rgba(58, 154, 255, 0) 34%),
|
||||
linear-gradient(154deg, rgba(28, 201, 154, 0.1) 18%, rgba(28, 201, 154, 0) 46%),
|
||||
linear-gradient(166deg, rgba(122, 84, 232, 0.1) 36%, rgba(122, 84, 232, 0) 58%),
|
||||
linear-gradient(174deg, rgba(214, 68, 104, 0.1) 56%, rgba(214, 68, 104, 0) 76%);
|
||||
opacity: 0.82;
|
||||
}
|
||||
|
||||
.theme-toggle-btn {
|
||||
box-shadow: 0 8px 20px rgba(6, 18, 31, 0.12) !important;
|
||||
}
|
||||
|
||||
.section,
|
||||
.quote-settings-bar,
|
||||
.sidebar,
|
||||
.mobile-panel-sheet,
|
||||
.confirm-modal-card {
|
||||
box-shadow:
|
||||
0 14px 36px rgba(2, 8, 17, 0.28),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.05) !important;
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,10 @@
|
||||
--card: #272420; /* elevated surface — clear separation from paper */
|
||||
--green: #3ab870;
|
||||
--amber: #e8920f;
|
||||
--sidebar-stack-gap: 12px;
|
||||
--sidebar-top-gap: calc(var(--sidebar-stack-gap) + 20px);
|
||||
--top-bar-sticky-offset: 62px;
|
||||
--sidebar-sticky-top: calc(var(--top-bar-sticky-offset) + var(--sidebar-top-gap));
|
||||
}
|
||||
body {
|
||||
background: var(--paper);
|
||||
@@ -111,14 +115,14 @@
|
||||
display: grid;
|
||||
grid-template-columns: 3fr 2fr;
|
||||
gap: 52px;
|
||||
padding: 50px clamp(20px,2vw,40px) 52px;
|
||||
padding: var(--sidebar-top-gap) clamp(20px,2vw,40px) 52px;
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
align-items: start;
|
||||
}
|
||||
.main-col { display: flex; flex-direction: column; gap: 28px; }
|
||||
.side-col { position: sticky; top: 102px; z-index: 10; align-self: start; }
|
||||
.sidebar-utility { margin-bottom: 12px; }
|
||||
.side-col { position: sticky; top: var(--sidebar-sticky-top); z-index: 10; align-self: start; }
|
||||
.sidebar-utility { margin-bottom: var(--sidebar-stack-gap); }
|
||||
.btn-reset-quote {
|
||||
width: 100%;
|
||||
background: var(--card);
|
||||
@@ -1459,7 +1463,7 @@
|
||||
@media (max-width: 1350px) {
|
||||
.outer {
|
||||
gap: 36px;
|
||||
padding: 52px clamp(48px,4vw,60px) 52px;
|
||||
padding: var(--sidebar-top-gap) clamp(48px,4vw,60px) 52px;
|
||||
}
|
||||
.section { margin-left: 76px; }
|
||||
.section-num { left: -76px; width: 64px; font-size: 54px; top: 30px; }
|
||||
|
||||
@@ -38,9 +38,9 @@
|
||||
.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.
|
||||
#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>
|
||||
|
||||
|
||||
@@ -1275,50 +1275,94 @@ function exportQuoteJSON() {
|
||||
}
|
||||
|
||||
// ── THEME TOGGLE ─────────────────────────────────────────────────
|
||||
// Light theme is a separate CSS file imported dynamically on demand.
|
||||
// Dark mode = base stylesheet only (no extra link element).
|
||||
// Light mode = base + SVS-MSP-Calculator-light.css.
|
||||
// Base CSS is the default dark theme.
|
||||
// Variant themes are imported dynamically on demand:
|
||||
// light = base + SVS-MSP-Calculator-light.css
|
||||
// glass = base + SVS-MSP-Calculator-glass.css
|
||||
// Preference persisted to localStorage under 'svs-theme'.
|
||||
// initTheme() called first so the page never flashes the wrong theme.
|
||||
// initTheme() runs before initQuote() so the UI boots in the saved theme.
|
||||
|
||||
const THEME_STORAGE_KEY = 'svs-theme';
|
||||
const THEME_STYLESHEET_ID = 'themeStylesheetLink';
|
||||
const THEME_ASSET_VERSION = '20260313-02';
|
||||
const SVG_SUN = '<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>';
|
||||
const SVG_MOON = '<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>';
|
||||
const SVG_GLASS = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l2.7 5.3L20 11l-5.3 2.7L12 19l-2.7-5.3L4 11l5.3-2.7L12 3z"/><path d="M18.5 3.5l.8 1.7L21 6l-1.7.8-.8 1.7-.8-1.7L16 6l1.7-.8.8-1.7z"/></svg>';
|
||||
const THEME_ORDER = ['dark', 'light', 'glass'];
|
||||
const THEME_CONFIG = {
|
||||
dark: {
|
||||
icon: SVG_MOON,
|
||||
href: null,
|
||||
label: 'Dark'
|
||||
},
|
||||
light: {
|
||||
icon: SVG_SUN,
|
||||
href: 'SVS-MSP-Calculator-light.css',
|
||||
label: 'Light'
|
||||
},
|
||||
glass: {
|
||||
icon: SVG_GLASS,
|
||||
href: 'SVS-MSP-Calculator-glass.css',
|
||||
label: 'Glass'
|
||||
}
|
||||
};
|
||||
|
||||
function toggleTheme() {
|
||||
const link = document.getElementById('lightThemeLink');
|
||||
function getSavedTheme() {
|
||||
const saved = localStorage.getItem(THEME_STORAGE_KEY);
|
||||
return THEME_ORDER.includes(saved) ? saved : 'dark';
|
||||
}
|
||||
|
||||
function getCurrentTheme() {
|
||||
const applied = document.documentElement.dataset.theme;
|
||||
return THEME_ORDER.includes(applied) ? applied : getSavedTheme();
|
||||
}
|
||||
|
||||
function updateThemeToggleUi(theme) {
|
||||
const icon = document.getElementById('themeToggleIcon');
|
||||
const btn = document.getElementById('themeToggle');
|
||||
if (link) {
|
||||
// Currently light → switch to dark
|
||||
link.remove();
|
||||
localStorage.setItem('svs-theme', 'dark');
|
||||
if (icon) icon.innerHTML = SVG_SUN;
|
||||
if (btn) btn.setAttribute('title', 'Switch to light theme');
|
||||
} else {
|
||||
// Currently dark → switch to light
|
||||
const el = document.createElement('link');
|
||||
el.id = 'lightThemeLink';
|
||||
el.rel = 'stylesheet';
|
||||
el.href = 'SVS-MSP-Calculator-light.css';
|
||||
document.head.appendChild(el);
|
||||
localStorage.setItem('svs-theme', 'light');
|
||||
if (icon) icon.innerHTML = SVG_MOON;
|
||||
if (btn) btn.setAttribute('title', 'Switch to dark theme');
|
||||
const currentIndex = THEME_ORDER.indexOf(theme);
|
||||
const nextTheme = THEME_ORDER[(currentIndex + 1) % THEME_ORDER.length];
|
||||
const currentLabel = THEME_CONFIG[theme].label;
|
||||
const nextLabel = THEME_CONFIG[nextTheme].label;
|
||||
if (icon) icon.innerHTML = THEME_CONFIG[theme].icon;
|
||||
if (btn) {
|
||||
const uiLabel = `Theme: ${currentLabel}. Click to switch to ${nextLabel}.`;
|
||||
btn.setAttribute('title', uiLabel);
|
||||
btn.setAttribute('aria-label', uiLabel);
|
||||
}
|
||||
}
|
||||
|
||||
function initTheme() {
|
||||
if (localStorage.getItem('svs-theme') === 'light') {
|
||||
function applyTheme(theme) {
|
||||
const nextTheme = THEME_ORDER.includes(theme) ? theme : 'dark';
|
||||
const existing = document.getElementById(THEME_STYLESHEET_ID);
|
||||
const legacyLight = document.getElementById('lightThemeLink');
|
||||
if (existing) existing.remove();
|
||||
if (legacyLight) legacyLight.remove();
|
||||
|
||||
const themeHref = THEME_CONFIG[nextTheme].href;
|
||||
if (themeHref) {
|
||||
const el = document.createElement('link');
|
||||
el.id = 'lightThemeLink';
|
||||
el.id = THEME_STYLESHEET_ID;
|
||||
el.rel = 'stylesheet';
|
||||
el.href = 'SVS-MSP-Calculator-light.css';
|
||||
el.href = `${themeHref}?v=${THEME_ASSET_VERSION}`;
|
||||
el.dataset.theme = nextTheme;
|
||||
document.head.appendChild(el);
|
||||
const icon = document.getElementById('themeToggleIcon');
|
||||
const btn = document.getElementById('themeToggle');
|
||||
if (icon) icon.innerHTML = SVG_MOON;
|
||||
if (btn) btn.setAttribute('title', 'Switch to dark theme');
|
||||
}
|
||||
|
||||
document.documentElement.dataset.theme = nextTheme;
|
||||
localStorage.setItem(THEME_STORAGE_KEY, nextTheme);
|
||||
updateThemeToggleUi(nextTheme);
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
const currentTheme = getCurrentTheme();
|
||||
const currentIndex = THEME_ORDER.indexOf(currentTheme);
|
||||
const nextTheme = THEME_ORDER[(currentIndex + 1) % THEME_ORDER.length];
|
||||
applyTheme(nextTheme);
|
||||
}
|
||||
|
||||
function initTheme() {
|
||||
applyTheme(getSavedTheme());
|
||||
}
|
||||
|
||||
// ── initQuote() ──────────────────────────────────────────────────
|
||||
@@ -1361,33 +1405,54 @@ initTheme();
|
||||
initQuote();
|
||||
|
||||
// ── MOBILE SIDEBAR SYNC CONTRACT ──────────────────────────────────────
|
||||
// Every stateful sidebar element has a mirror ID with _m suffix.
|
||||
// The update() wrapper below syncs _m elements after each call to _origUpdate().
|
||||
// The desktop sidebar is the single markup source of truth.
|
||||
// On boot, we clone it into #mobilePanelContent and suffix all IDs with _m.
|
||||
// The update() wrapper below then syncs dynamic values/classes into the clone.
|
||||
//
|
||||
// WHEN ADDING A NEW SIDEBAR ELEMENT:
|
||||
// 1. Add the desktop element with its ID (e.g. #my-element)
|
||||
// 2. Add the mobile duplicate in #mobilePanelContent with ID #my-element_m
|
||||
// 3. Add the appropriate sync call in the update() wrapper:
|
||||
// syncEl(id) — copies innerHTML (text/HTML values)
|
||||
// syncClass(id) — copies className (.hidden toggling via classList)
|
||||
// syncStyle(id) — copies style.cssText (legacy inline display — avoid for new elements)
|
||||
// syncChecked(id) — copies .checked state (checkboxes)
|
||||
//
|
||||
// NEVER DOM-move the real .sidebar into the panel.
|
||||
// The duplicate HTML is intentional — moving breaks desktop layout on resize.
|
||||
// 2. Ensure the mobile sync map below includes it if it changes at runtime
|
||||
// 3. Avoid separate handwritten mobile markup for sidebar content
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
// ── MOBILE QUOTE PANEL IIFE ──────────────────────────────────────
|
||||
// Encapsulates all mobile panel logic to avoid polluting global scope.
|
||||
// ARCHITECTURE:
|
||||
// The real sidebar lives in .side-col (desktop).
|
||||
// The panel contains a STATIC DUPLICATE with _m suffixed IDs.
|
||||
// The mobile panel gets a JS-generated clone with _m suffixed IDs.
|
||||
// update() is wrapped here to sync _m elements after every update.
|
||||
// openMobilePanel / closeMobilePanel are exposed on window.
|
||||
// Do NOT DOM-move the real sidebar into the panel —
|
||||
// Do NOT DOM-move the real desktop sidebar into the panel —
|
||||
// it permanently breaks desktop layout on resize.
|
||||
(function() {
|
||||
// Panel uses a static duplicate sidebar (_m IDs) — no DOM moving needed.
|
||||
function buildMobileSidebar() {
|
||||
var container = document.getElementById('mobilePanelContent');
|
||||
var desktopSidebar = document.querySelector('.side-col .sidebar');
|
||||
if (!container || !desktopSidebar || container.children.length) return;
|
||||
|
||||
var mobileSidebar = desktopSidebar.cloneNode(true);
|
||||
mobileSidebar.removeAttribute('id');
|
||||
|
||||
mobileSidebar.querySelectorAll('[id]').forEach(function(el) {
|
||||
el.id = el.id + '_m';
|
||||
});
|
||||
|
||||
var mobileHstToggle = mobileSidebar.querySelector('#hstToggle_m');
|
||||
if (mobileHstToggle) {
|
||||
mobileHstToggle.onchange = function() {
|
||||
var desktopHstToggle = document.getElementById('hstToggle');
|
||||
if (desktopHstToggle) desktopHstToggle.checked = this.checked;
|
||||
update();
|
||||
};
|
||||
}
|
||||
|
||||
var mobileExportJson = mobileSidebar.querySelector('#btnExportJSON_m');
|
||||
if (mobileExportJson) mobileExportJson.removeAttribute('id');
|
||||
|
||||
container.appendChild(mobileSidebar);
|
||||
}
|
||||
|
||||
buildMobileSidebar();
|
||||
|
||||
window.openMobilePanel = function() {
|
||||
var panel = document.getElementById('mobileQuotePanel');
|
||||
@@ -1437,6 +1502,84 @@ initQuote();
|
||||
if (src && dst) dst.checked = src.checked;
|
||||
}
|
||||
|
||||
var sidebarSyncMap = {
|
||||
html: [
|
||||
'clientNameDisplay',
|
||||
'sl-users-val',
|
||||
'sl-endpoints-val',
|
||||
'sl-servers-val',
|
||||
'sl-zt-val',
|
||||
'sl-voip-val',
|
||||
'sl-admin-val',
|
||||
'mrrDisplay',
|
||||
'annualDisplay',
|
||||
'perUserDisplay',
|
||||
'perUserBreakdown',
|
||||
'm365SaveAmt',
|
||||
'sl-discount-val',
|
||||
'sl-base-mrr-val',
|
||||
'sl-hst-val',
|
||||
'sl-hst-total-val',
|
||||
'sl-otf-val',
|
||||
'vs-svs-annual',
|
||||
'vs-1man-cost',
|
||||
'vs-1man-save',
|
||||
'vs-1man-save-lbl',
|
||||
'vs-5man-cost',
|
||||
'vs-5man-save',
|
||||
'vs-5man-save-lbl',
|
||||
'vs-footnote',
|
||||
'nudgeText',
|
||||
'nudgeCounter',
|
||||
'sl-users-sub',
|
||||
'sl-endpoints-sub',
|
||||
'sl-admin-sub',
|
||||
'adminWaivedAmt'
|
||||
],
|
||||
class: [
|
||||
'sl-users',
|
||||
'sl-users-sub',
|
||||
'sl-endpoints',
|
||||
'sl-endpoints-sub',
|
||||
'sl-admin-sub',
|
||||
'sl-servers',
|
||||
'sl-zt',
|
||||
'sl-voip',
|
||||
'sl-admin',
|
||||
'sideNote-m365',
|
||||
'sideNote-byol',
|
||||
'vsComparison',
|
||||
'perUserRow',
|
||||
'perUserBreakdown',
|
||||
'sl-discount-row',
|
||||
'sl-base-mrr-row',
|
||||
'sl-hst-row',
|
||||
'sl-hst-total-row',
|
||||
'sl-otf-row',
|
||||
'vs-1man-save-row',
|
||||
'vs-1man-save',
|
||||
'vs-1man-save-lbl',
|
||||
'vs-5man-save-row',
|
||||
'vs-5man-save',
|
||||
'vs-5man-save-lbl',
|
||||
'nudgeBanner',
|
||||
'adminWaivedSavings'
|
||||
],
|
||||
style: [
|
||||
'sl-users-sub',
|
||||
'sl-endpoints-sub',
|
||||
'sl-admin-sub',
|
||||
'perUserRow'
|
||||
],
|
||||
checked: [
|
||||
'hstToggle'
|
||||
]
|
||||
};
|
||||
|
||||
function runSidebarSync(ids, syncFn) {
|
||||
ids.forEach(syncFn);
|
||||
}
|
||||
|
||||
// ── UPDATE WRAPPER ─────────────────────────────────────────────
|
||||
// Wraps the global update() to also sync the mobile panel.
|
||||
// _origUpdate = the real update() defined above.
|
||||
@@ -1445,70 +1588,10 @@ initQuote();
|
||||
var _origUpdate = window.update;
|
||||
window.update = function() {
|
||||
_origUpdate();
|
||||
// Sync all mirrored sidebar elements
|
||||
syncEl('clientNameDisplay');
|
||||
syncEl('sl-users-val');
|
||||
syncEl('sl-endpoints-val');
|
||||
syncEl('sl-servers-val');
|
||||
syncEl('sl-zt-val');
|
||||
syncEl('sl-voip-val');
|
||||
syncEl('sl-admin-val');
|
||||
syncEl('mrrDisplay');
|
||||
syncEl('annualDisplay');
|
||||
syncEl('perUserDisplay');
|
||||
syncEl('perUserBreakdown');
|
||||
syncEl('m365SaveAmt');
|
||||
syncEl('sl-discount-val');
|
||||
syncEl('sl-base-mrr-val');
|
||||
syncEl('sl-hst-val');
|
||||
syncEl('sl-hst-total-val');
|
||||
syncEl('sl-otf-val');
|
||||
syncEl('vs-svs-annual');
|
||||
syncEl('vs-1man-cost');
|
||||
syncEl('vs-1man-save');
|
||||
syncEl('vs-1man-save-lbl');
|
||||
syncEl('vs-5man-cost');
|
||||
syncEl('vs-5man-save');
|
||||
syncEl('vs-5man-save-lbl');
|
||||
syncEl('vs-footnote');
|
||||
syncEl('nudgeText');
|
||||
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');
|
||||
syncClass('sl-admin');
|
||||
syncClass('sideNote-m365');
|
||||
syncClass('sideNote-byol');
|
||||
syncClass('vsComparison');
|
||||
syncClass('perUserRow');
|
||||
syncClass('perUserBreakdown');
|
||||
syncClass('sl-discount-row');
|
||||
syncClass('sl-base-mrr-row');
|
||||
syncClass('sl-hst-row');
|
||||
syncClass('sl-hst-total-row');
|
||||
syncClass('sl-otf-row');
|
||||
syncClass('vs-1man-save-row');
|
||||
syncClass('vs-1man-save');
|
||||
syncClass('vs-1man-save-lbl');
|
||||
syncClass('vs-5man-save-row');
|
||||
syncClass('vs-5man-save');
|
||||
syncClass('vs-5man-save-lbl');
|
||||
syncClass('nudgeBanner');
|
||||
syncClass('adminWaivedSavings');
|
||||
syncEl('adminWaivedAmt');
|
||||
syncStyle('sl-users-sub');
|
||||
syncStyle('sl-endpoints-sub');
|
||||
syncStyle('sl-admin-sub');
|
||||
syncStyle('perUserRow');
|
||||
syncChecked('hstToggle');
|
||||
runSidebarSync(sidebarSyncMap.html, syncEl);
|
||||
runSidebarSync(sidebarSyncMap.class, syncClass);
|
||||
runSidebarSync(sidebarSyncMap.style, syncStyle);
|
||||
runSidebarSync(sidebarSyncMap.checked, syncChecked);
|
||||
// Pill MRR — show effective MRR with label
|
||||
var mrr = document.getElementById('mrrDisplay');
|
||||
var pill = document.getElementById('mobilePillMrr');
|
||||
|
||||
Reference in New Issue
Block a user