1/* pricing-savings.js 2 * Savings comparison slider for the "Your price never goes up as you grow" card. 3 * Loaded by index.html (homepage pricing) and pricing.html. Neither loads home.js, 4 * so this is a standalone widget. Pure helpers are exported for node testing; 5 * the DOM wiring runs only in a browser. 6 */ 7(function (global) { 8 'use strict'; 9 10 var SMIN = 25000; // slider floor: $25k/mo (breakeven, savings always >= 0) 11 var SMAX = 5000000; // slider ceiling: $5M/mo 12 var COMP_RATE = 0.006; // usage-based platforms ~ 0.6% of monthly ad spend 13 var TM_YEARLY = 1600; // TrueMetriks Unlimited, flat per year 14 var LOGMIN = Math.log10(SMIN); 15 var LOGMAX = Math.log10(SMAX); 16 17 // round to 2 significant figures (same as the flying-blind slider) 18 function niceRound(n) { 19 if (n <= 0) return 0; 20 var step = Math.pow(10, Math.floor(Math.log10(n)) - 1); 21 return Math.round(n / step) * step; 22 } 23 24 // slider position (0-1000) -> monthly ad spend, nice-rounded 25 function spendFromValue(value) { 26 var t = Math.min(1, Math.max(0, (+value) / 1000)); 27 return niceRound(Math.pow(10, LOGMIN + t * (LOGMAX - LOGMIN))); 28 } 29 30 // monthly ad spend -> usage-based platform yearly cost, nice-rounded 31 function competitorYearly(spend) { 32 return niceRound(spend * COMP_RATE * 12); 33 } 34 35 // savings = competitor yearly - TrueMetriks yearly (never negative) 36 function savingsYearly(competitor) { 37 return Math.max(0, competitor - TM_YEARLY); 38 } 39 40 // "$7,200" / "$1.2M" / "$5M" 41 function fmtUSD(n) { 42 if (n >= 1000000) { 43 return '$' + (n / 1000000).toFixed(n % 1000000 === 0 ? 0 : 1).replace(/\.0$/, '') + 'M'; 44 } 45 return '$' + Math.round(n).toLocaleString('en-US'); 46 } 47 48 var api = { 49 SMIN: SMIN, SMAX: SMAX, COMP_RATE: COMP_RATE, TM_YEARLY: TM_YEARLY, 50 niceRound: niceRound, spendFromValue: spendFromValue, 51 competitorYearly: competitorYearly, savingsYearly: savingsYearly, fmtUSD: fmtUSD 52 }; 53 if (typeof module !== 'undefined' && module.exports) module.exports = api; 54 55 if (typeof document === 'undefined') return; // node test path stops here 56 57 function init() { 58 var range = document.getElementById('sav-range'); 59 if (!range) return; 60 var spendEl = document.getElementById('sav-spend'); 61 var compEl = document.getElementById('sav-comp'); 62 var saveEl = document.getElementById('sav-save'); 63 if (!spendEl || !compEl || !saveEl) return; 64 65 function update() { 66 var t = (+range.value) / 1000; 67 var spend = spendFromValue(range.value); 68 var comp = competitorYearly(spend); 69 var save = savingsYearly(comp); 70 spendEl.textContent = fmtUSD(spend); 71 compEl.textContent = fmtUSD(comp) + '/year'; 72 saveEl.textContent = fmtUSD(save); 73 range.style.setProperty('--fill', (t * 100) + '%'); 74 } 75 range.addEventListener('input', update); 76 update(); 77 } 78 79 if (document.readyState !== 'loading') init(); 80 else document.addEventListener('DOMContentLoaded', init); 81})(this);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.