1/* free-riddim-popup.js (28 Sep 2026) â riddimBank's own free-riddim popup, 2 replacing OptiMonk. Loaded on every page from _includes/_head.njk. 3 4 SHOWS: after 45 seconds on a page, or (desktop) when the mouse leaves the top 5 of the window, whichever is first. At most once every 7 days. Never again after 6 the visitor signs up, or after they close it 3 times. 7 NEVER ON: /free-riddim*, cart, checkout/success, account, set-password, 8 manage-subscription, admin-*, *-download pages, contributor/referrer/contractor portals, 9 or for anyone signed in (Firebase Auth). 10 NO TRACKING: nothing is sent anywhere until the visitor submits the form. It 11 only keeps a small note in this browser (localStorage "rbFreeRiddimPopup") so it 12 doesn't nag, which is why it needs no cookie consent and shows to everyone. 13 Testing: add ?rbpopup=now to any page to see it straight away. 14 The form posts to the same Cloud Function as /free-riddim (freeRiddimSignup), 15 so delivery is instant (Mailgun) and the person lands on the INBOX list 16 "Free riddim (website)". Kill switch: set window.RB_NO_POPUP = true. */ 17(function () { 18 if (window.RB_NO_POPUP) return; 19 var ENDPOINT = 'https://us-central1-riddimbank-webauth.cloudfunctions.net/freeRiddimSignup'; 20 var KEY = 'rbFreeRiddimPopup'; 21 var DELAY_MS = 45000, GAP_MS = 7 * 24 * 60 * 60 * 1000, MAX_CLOSES = 3; 22 var path = location.pathname; 23 if (/^\/(free-riddim|cart|checkout|success|account|set-password|manage-subscription|admin|contributor-portal|referrer-portal|contractor-portal|deposit|upload|become-a-contributor|become-a-referrer)/.test(path) 24 || /-download\/?$/.test(path)) return; 25 26 function load() { try { return JSON.parse(localStorage.getItem(KEY) || '{}'); } catch (e) { return {}; } } 27 function save(s) { try { localStorage.setItem(KEY, JSON.stringify(s)); } catch (e) {} } 28 var st = load(); 29 var force = /[?&]rbpopup=now\b/.test(location.search); /* testing: ?rbpopup=now shows it at once, ignoring the rules above */ 30 if (force) { st = {}; } 31 else if (st.signedUp || (st.closes || 0) >= MAX_CLOSES) return; 32 if (st.lastShown && Date.now() - st.lastShown < GAP_MS) return; 33 34 function signedIn() { 35 try { return !!(window.firebase && firebase.auth && firebase.auth().currentUser); } catch (e) { return false; } 36 } 37 38 var shown = false, timer = null; 39 function show() { 40 if (shown || (!force && signedIn())) return; 41 shown = true; 42 clearTimeout(timer); 43 document.removeEventListener('mouseout', onLeave); 44 st.lastShown = Date.now(); save(st); 45 build(); 46 } 47 function onLeave(e) { if (!e.relatedTarget && e.clientY <= 0) show(); } 48 49 function build() { 50 var css = document.createElement('style'); 51 css.textContent = 52 '.rbfr-ov{position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:99999;display:flex;align-items:center;justify-content:center;padding:16px;animation:rbfrIn .25s ease}' + 53 '@keyframes rbfrIn{from{opacity:0}to{opacity:1}}' + 54 '.rbfr-box{position:relative;width:100%;max-width:440px;background:#0a0a0a;border:1px solid #2a2a2a;border-radius:14px;padding:28px 24px 22px;color:#f2f2f2;font-family:Arial,Helvetica,sans-serif;box-shadow:0 20px 60px rgba(0,0,0,.6)}' + 55 '.rbfr-x{position:absolute;top:10px;right:12px;background:none;border:0;color:#aaa;font-size:26px;line-height:1;cursor:pointer;padding:4px}' + 56 '.rbfr-k{margin:0;color:#1DB954;text-transform:uppercase;letter-spacing:.08em;font-size:12px}' + 57 '.rbfr-h{margin:6px 0 8px;font-family:"Archivo Black","Arial Black",Arial,sans-serif;font-size:26px;line-height:1.15;color:#fff}' + 58 '.rbfr-p{margin:0 0 16px;color:#ccc;font-size:15px;line-height:1.5}' + 59 '.rbfr-box input[type=text],.rbfr-box input[type=email]{width:100%;box-sizing:border-box;padding:12px;margin:0 0 10px;border-radius:8px;border:1px solid #333;background:#000;color:#fff;font-size:16px}' + 60 '.rbfr-box input:focus{outline:2px solid #1DB954;outline-offset:1px}' + 61 '.rbfr-hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}' + 62 '.rbfr-btn{display:block;width:100%;background:#1DB954;color:#000;border:0;border-radius:8px;padding:14px;font-weight:700;font-size:17px;cursor:pointer}' + 63 '.rbfr-btn:disabled{opacity:.6}' + 64 '.rbfr-s{margin:10px 0 0;color:#888;font-size:12px;line-height:1.4}.rbfr-s a{color:#1DB954}' + 65 '.rbfr-m{margin:8px 0 0;color:#ffcf66;font-size:14px;min-height:1em}'; 66 document.head.appendChild(css); 67 68 var ov = document.createElement('div'); 69 ov.className = 'rbfr-ov'; 70 ov.setAttribute('role', 'dialog'); 71 ov.setAttribute('aria-modal', 'true'); 72 ov.setAttribute('aria-labelledby', 'rbfrTitle'); 73 ov.innerHTML = 74 '<div class="rbfr-box">' + 75 '<button type="button" class="rbfr-x" aria-label="Close">×</button>' + 76 '<div class="rbfr-body">' + 77 '<p class="rbfr-k">Free for you</p>' + 78 '<h2 class="rbfr-h" id="rbfrTitle">Get a FREE multitrack riddim</h2>' + 79 '<p class="rbfr-p">“First Class Citizen”, recorded for Edi Fitzroy’s final album. 12 tracks, 24-bit WAV. We’ll email it to you in seconds.</p>' + 80 '<form novalidate>' + 81 '<input type="text" name="firstName" placeholder="First name" autocomplete="given-name" maxlength="60" aria-label="First name">' + 82 '<input type="email" name="email" placeholder="Email" autocomplete="email" maxlength="200" aria-label="Email">' + 83 '<div class="rbfr-hp" aria-hidden="true"><input name="website" tabindex="-1" autocomplete="off"><input name="url" tabindex="-1" autocomplete="off"><input name="phone_number" tabindex="-1" autocomplete="off"></div>' + 84 '<button type="submit" class="rbfr-btn">Send me the free riddim →</button>' + 85 '<p class="rbfr-m" role="status"></p>' + 86 '</form>' + 87 '<p class="rbfr-s">We’ll add you to our newsletter. Unsubscribe any time. <a href="/free-riddim/">See what’s included</a></p>' + 88 '</div>' + 89 '</div>'; 90 document.body.appendChild(ov); 91 92 var box = ov.querySelector('.rbfr-box'), form = ov.querySelector('form'), msg = ov.querySelector('.rbfr-m'), btn = ov.querySelector('.rbfr-btn'); 93 var lastFocus = document.activeElement; 94 function close(counted) { 95 if (counted) { st.closes = (st.closes || 0) + 1; save(st); } 96 ov.remove(); 97 document.removeEventListener('keydown', onKey); 98 if (lastFocus && lastFocus.focus) try { lastFocus.focus(); } catch (e) {} 99 } 100 function onKey(e) { if (e.key === 'Escape') close(true); } 101 document.addEventListener('keydown', onKey); 102 ov.querySelector('.rbfr-x').addEventListener('click', function () { close(true); }); 103 ov.addEventListener('click', function (e) { if (e.target === ov) close(true); }); 104 setTimeout(function () { var f = form.querySelector('input[name=firstName]'); if (f) f.focus(); }, 50); 105 106 form.addEventListener('submit', function (e) { 107 e.preventDefault(); 108 msg.textContent = ''; 109 var fd = new FormData(form); 110 var firstName = String(fd.get('firstName') || '').trim(), email = String(fd.get('email') || '').trim(); 111 if (!firstName) { msg.textContent = 'Please enter your first name.'; return; } 112 if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) { msg.textContent = 'Please enter a valid email address.'; return; } 113 btn.disabled = true; btn.textContent = 'Sendingâ¦'; 114 fetch(ENDPOINT, { 115 method: 'POST', headers: { 'Content-Type': 'application/json' }, 116 body: JSON.stringify({ firstName: firstName, email: email, website: fd.get('website') || '', url: fd.get('url') || '', phone_number: fd.get('phone_number') || '', 117 fromPage: 'popup:' + location.pathname, referrer: document.referrer || '' }) 118 }).then(function (r) { return r.json().catch(function () { return {}; }).then(function (j) { return { ok: r.ok, j: j }; }); }) 119 .then(function (res) { 120 if (res.ok && res.j.success) { 121 st.signedUp = true; save(st); 122 ov.querySelector('.rbfr-body').innerHTML = 123 '<p class="rbfr-k">On its way</p><h2 class="rbfr-h">Check your inbox</h2>' + 124 '<p class="rbfr-p">Your free riddim should arrive within a minute. If you don’t see it, check your spam or promotions folder.</p>' + 125 '<button type="button" class="rbfr-btn">Keep browsing</button>'; 126 ov.querySelector('.rbfr-body .rbfr-btn').addEventListener('click', function () { close(false); }); 127 return; 128 } 129 msg.textContent = res.j.error || 'Something went wrong. Please try again in a minute.'; 130 btn.disabled = false; btn.innerHTML = 'Send me the free riddim →'; 131 }) 132 .catch(function () { msg.textContent = 'Could not connect. Please try again.'; btn.disabled = false; btn.innerHTML = 'Send me the free riddim →'; }); 133 }); 134 } 135 136 timer = setTimeout(show, force ? 300 : DELAY_MS); 137 if (window.matchMedia && window.matchMedia('(pointer:fine)').matches) document.addEventListener('mouseout', onLeave); 138})();
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.