PageSourceSearch

https://get.rightnowmedia.org/shared/js/utils.js

js rightnowmedia.org collected 2026-09-24 09:21:07 UTC 4,762 bytes, 111 lines download raw bytes

1function superDollar(str) {
2  if (!str) return str;
3  var clean = str.replace('$', '');
4  var dot = clean.indexOf('.');
5  if (dot === -1) return '<span class="co-summary-dollar">$</span>' + clean;
6  return '<span class="co-summary-dollar">$</span>' + clean.slice(0, dot) + '<span class="co-summary-cents">.' + clean.slice(dot + 1) + '</span>';
7}
8
9// Whole-dollar prices read better without the cents — "$25", not "$25.00".
10// Only a flat-price brand hits this today (Microchurch); every tier on the
11// other brands ends .49 or .99, so they are unaffected.
12function formatMoney(n) {
13  var s = n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
14  return '$' + s.replace(/\.00$/, '');
15}
16
17function _storageKey(name) {
18  var prefix = (window.BRAND_CONFIG && window.BRAND_CONFIG.storagePrefix) || 'rnm';
19  return prefix + '_' + name;
20}
21
22function saveBilling(v) { sessionStorage.setItem(_storageKey('billing'), v); }
23// Brands may open on either cycle: BRAND_CONFIG.defaultBilling picks. Microchurch
24// opens monthly ($25 reads easier than $275); everyone else opens annual.
25function loadBilling() {
26  var fallback = (window.BRAND_CONFIG && window.BRAND_CONFIG.defaultBilling) || 'annual';
27  return sessionStorage.getItem(_storageKey('billing')) || fallback;
28}
29
30// A campaign link may preset the billing cycle: /checkout/?billing=monthly.
31// Called once on step 1, BEFORE the first loadBilling(), and deliberately
32// writes through to session storage rather than just returning the value:
33// step 2 reads the cycle back out of storage to pick the Chargebee plan id, so
34// a cycle that only reached the toggle would show one price and charge the
35// other. An email's stated intent outranks a cycle stored on an earlier visit,
36// which is why this overwrites.
37//
38// Case and stray whitespace are forgiven ("?billing=Monthly" is a link someone
39// hand-typed, not a different intent), but anything that is not one of the two
40// cycles is ignored outright — writing a junk value here would flow straight
41// into getTierData and the plan id lookup.
42function applyBillingFromUrl() {
43  var raw;
44  try {
45    raw = new URLSearchParams(window.location.search).get('billing');
46  } catch (e) {
47    return null;
48  }
49  if (raw == null) return null;
50  var cycle = String(raw).trim().toLowerCase();
51  if (cycle !== 'annual' && cycle !== 'monthly') return null;
52  saveBilling(cycle);
53  return cycle;
54}
55
56function saveTier(index) { sessionStorage.setItem(_storageKey('tier'), String(index)); }
57// The fallback is clamped to the tier count: a brand with fewer than four tiers
58// (Microchurch has one) would otherwise get index 3 and read TIERS[3] as
59// undefined. BRAND_CONFIG.defaultTierIndex overrides where a brand wants the
60// slider to open somewhere other than the fourth tier.
61function loadTier() {
62  var raw = sessionStorage.getItem(_storageKey('tier'));
63  var i = parseInt(raw, 10);
64  if (!isNaN(i) && i >= 0 && i < TIERS.length) return i;
65  var wanted = (window.BRAND_CONFIG && window.BRAND_CONFIG.defaultTierIndex);
66  if (wanted == null) wanted = 3;
67  return Math.max(0, Math.min(wanted, TIERS.length - 1));
68}
69
70function getTierData(index, billing) {
71  var t = TIERS[index];
72  var last = TIERS.length - 1;
73  var isAnnual = (billing || loadBilling()) === 'annual';
74
75  var monthly = isAnnual ? t.price : t.monthlyPrice;
76  var annual  = t.annualTotal != null ? t.annualTotal : (t.price != null ? t.price * 12 : null);
77  var whole   = monthly != null ? Math.floor(monthly).toLocaleString('en-US') : null;
78  var cents   = monthly != null
79    ? String(Math.round((monthly - Math.floor(monthly)) * 100)).padStart(2, '0')
80    : null;
81  // null rather than '00' so callers can omit the cents span entirely.
82  if (cents === '00') cents = null;
83
84  var savingsPct = 0;
85  if (t.annualTotal != null && t.monthlyPrice != null) {
86    savingsPct = Math.round((1 - t.annualTotal / (t.monthlyPrice * 12)) * 100);
87  } else if (t.price != null && t.monthlyPrice != null) {
88    savingsPct = Math.round((1 - t.price / t.monthlyPrice) * 100);
89  }
90
91  var due = new Date();
92  due.setDate(due.getDate() + 30);
93  var firstPaymentDate = due.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
94
95  return {
96    tier: t,
97    tierIndex: index,
98    // A single-tier brand has last === 0; dividing by it yields NaN.
99    pctStr: (last > 0 ? (index / last * 100) : 0).toFixed(3) + '%',
100    tierLabel: t.label,
101    isStd:  t.kind === 'std',
102    isCall: t.kind === 'call',
103    isAnnual: isAnnual,
104    monthlyStr:   monthly != null ? formatMoney(monthly) : null,
105    monthlyWhole: whole,
106    monthlyCents: cents,
107    annualStr:    annual  != null ? formatMoney(annual)  : null,
108    savingsPct:   savingsPct,
109    firstPaymentDate: firstPaymentDate,
110  };
111}

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.