1// ===================================================================== 2// CURRENCY PREFERENCE â INR for India, USD for everyone else 3// ===================================================================== 4// Centralizes the currency-detection + override logic so the landing 5// pricing section, the checkout page, and any future Stripe-touching 6// code all read from the same source of truth. 7// 8// Detection ladder (first match wins): 9// 1. URL ?currency=inr (one-shot override, also persists) 10// 2. localStorage 'tsai-currency-pref' (sticky user choice) 11// 3. Browser timezone === 'Asia/Kolkata' â INR 12// 4. Default â USD 13// 14// Public API: 15// window.TSAI_CURRENCY.get() â 'inr' | 'usd' 16// window.TSAI_CURRENCY.set('inr') â persists + dispatches event 17// window.TSAI_CURRENCY.format(val) â "â¹1,499" or "$19" 18// window.TSAI_CURRENCY.symbol() â "â¹" or "$" 19// window.TSAI_CURRENCY.code() â "INR" or "USD" 20// window.TSAI_CURRENCY.detected() â 'inr' | 'usd' (without override) 21// addEventListener('tsai-currency-change', e => ...) on window 22// ===================================================================== 23(function () { 24 const STORAGE_KEY = 'tsai-currency-pref'; 25 const VALID = ['usd', 'inr']; 26 27 // INR (and the Razorpay/GST path) is India-only. Used to gate both the 28 // detected currency and whether the on-page INR toggle is shown at all. 29 function isIndia() { 30 try { 31 const tz = Intl.DateTimeFormat().resolvedOptions().timeZone || ''; 32 return tz === 'Asia/Kolkata' || tz === 'Asia/Calcutta'; 33 } catch (_) { return false; } 34 } 35 36 function detected() { 37 return isIndia() ? 'inr' : 'usd'; 38 } 39 40 // ONE-SHOT URL override at script init: if ?currency= is present 41 // and valid, persist it to localStorage immediately. Subsequent 42 // get() calls only read localStorage / detected timezone, so the 43 // toggle's set() call wins (URL doesn't keep "voting" forever). 44 (function consumeUrlOverride() { 45 try { 46 const u = new URL(location.href); 47 const c = (u.searchParams.get('currency') || '').toLowerCase(); 48 if (VALID.includes(c)) { 49 localStorage.setItem(STORAGE_KEY, c); 50 } 51 } catch (_) {} 52 })(); 53 54 function get() { 55 if (!isIndia()) return 'usd'; // INR is India-only â ignore any sticky pref elsewhere 56 try { 57 const v = (localStorage.getItem(STORAGE_KEY) || '').toLowerCase(); 58 if (VALID.includes(v)) return v; 59 } catch (_) {} 60 return detected(); 61 } 62 63 function set(c) { 64 let next = String(c || '').toLowerCase(); 65 if (!VALID.includes(next)) return; 66 if (next === 'inr' && !isIndia()) next = 'usd'; // never allow INR outside India 67 68 const prev = get(); 69 try { localStorage.setItem(STORAGE_KEY, next); } catch (_) {} 70 if (prev !== next) { 71 try { 72 window.dispatchEvent(new CustomEvent('tsai-currency-change', { 73 detail: { from: prev, to: next }, 74 })); 75 } catch (_) {} 76 } 77 } 78 79 function symbol(c) { 80 return (c || get()) === 'inr' ? 'â¹' : '$'; 81 } 82 function code(c) { 83 return (c || get()) === 'inr' ? 'INR' : 'USD'; 84 } 85 86 // format(amount, currency?) â returns symbol + thousands-separated value 87 // Whole-rupee/whole-dollar amounts elide ".00" (matches the fmt$ rule 88 // in app.js â keeps the headline price scannable on small screens). 89 function format(amount, c) { 90 const cur = c || get(); 91 const sym = cur === 'inr' ? 'â¹' : '$'; 92 const abs = Math.abs(+amount || 0); 93 const isWhole = abs % 1 === 0; 94 const formatted = abs.toLocaleString(cur === 'inr' ? 'en-IN' : 'en-US', {
95 minimumFractionDigits: isWhole ? 0 : 2, 96 maximumFractionDigits: 2, 97 }); 98 return (amount < 0 ? '-' : '') + sym + formatted; 99 } 100 101 window.TSAI_CURRENCY = { 102 get, 103 set, 104 symbol, 105 code, 106 format, 107 detected, 108 isIndia, 109 VALID, 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.