1// retention.js â the free-save funnel: sign-in gate modal, "don't lose it" banner, 2// slot meter, and local->cloud migration toasts. Shared by index / gachapon / 3// claw-machine / photo-booth across all four locales. 4// 5// 2026-08-20: cloud saves went FREE (3 slots) on gacha / claw / photo booth â they were 6// Pro-only, which left free users with nothing stored and nothing to come back for 7// (93 accounts in 3 months). This file turns a signed-out "save" click into an account 8// instead of a dead-end toast, and tells device-bound users their setup can be kept. 9// 10// Every entry point is a no-op on OBS / embed / preview surfaces. 11(function () { 12 'use strict'; 13 var Q = new URLSearchParams(location.search); 14 var MUTED = Q.get('obs') === '1' || Q.get('obs') === 'true' 15 || Q.get('embed') === '1' || Q.get('preview') === '1'; 16 var LANG = (location.pathname.match(/^\/(ja|th|ko)\//) || [0, 'en'])[1]; 17 18 var STR = { 19 en: { 20 nounwheel: 'wheel', noungacha: 'gachapon', nounclaw: 'claw machine', nounphotobooth: 'photo booth', nounthankyou: 'thank-you card', 21 gateTitle: 'Save it â free', 22 gateSub: 'Sign in with Google to keep this {noun} in your account.', 23 b1: '3 saved slots, free forever', b2: 'Opens on any device â nothing dies with this browser', 24 b3: 'Ready for OBS & donation triggers later', 25 googleBtn: 'Continue with Google', noCard: 'Free · no card needed', gateSuccess: 'Signed in â', 26 bnwheel: "This wheel isn't saved anywhere yet â sign in free to keep it.", 27 bn: 'Your {noun} lives only on this device â sign in free so it canât be lost.', 28 bnBtn: 'Sign in free', 29 migrated: 'âï¸ Found the {noun} you set up on this device â saved to your account.', 30 meterFree: '{n}/3 saved · Pro = unlimited', meterFull: '3/3 full â Pro = unlimited', meterPro: '{n} saved', 31 boothPrompt: '{n} photos live only on this device â save your booth to keep them.', boothPromptBtn: 'Save booth' 32 }, 33 th: { 34 nounwheel: 'วà¸à¸¥à¹à¸', noungacha: 'à¸à¸¹à¹à¸à¸²à¸à¸²', nounclaw: 'à¸à¸¹à¹à¸à¸µà¸', nounphotobooth: 'à¸à¸¹à¹à¸à¹à¸²à¸¢à¸£à¸¹à¸', nounthankyou: 'à¸à¸²à¸£à¹à¸à¸à¸à¸à¸à¸¸à¸', 35 gateTitle: 'à¹à¸à¸à¹à¸à¹à¸à¹à¸§à¹ â à¸à¸£à¸µ', 36 gateSub: 'Sign in à¸à¹à¸§à¸¢ Google à¹à¸à¸·à¹à¸à¹à¸à¹à¸{noun}à¸à¸µà¹à¹à¸§à¹à¹à¸à¸à¸±à¸à¸à¸µà¸à¸à¸à¸à¸¸à¸', 37 b1: 'à¹à¸à¸à¹à¸à¹ 3 à¸à¹à¸à¸ à¸à¸£à¸µà¸à¸¥à¸à¸', b2: 'à¹à¸à¸´à¸à¹à¸à¹à¸à¸¸à¸à¹à¸à¸£à¸·à¹à¸à¸ à¹à¸¡à¹à¸«à¸²à¸¢à¹à¸à¸à¸±à¸à¹à¸à¸£à¸²à¸§à¹à¹à¸à¸à¸£à¹', 38 b3: 'à¸à¸£à¹à¸à¸¡à¸à¹à¸ OBS à¹à¸¥à¸°à¹à¸à¹à¸à¸à¹à¸à¹à¸à¸µà¸«à¸¥à¸±à¸', 39 googleBtn: 'à¸à¸³à¹à¸à¸´à¸à¸à¸²à¸£à¸à¹à¸à¸à¹à¸§à¸¢ Google', noCard: 'à¸à¸£à¸µ · à¹à¸¡à¹à¸à¹à¸à¸à¹à¸à¹à¸à¸±à¸à¸£', gateSuccess: 'à¹à¸à¹à¸²à¸ªà¸¹à¹à¸£à¸°à¸à¸à¹à¸¥à¹à¸§ â', 40 bnwheel: 'วà¸à¸¥à¹à¸à¸à¸µà¹à¸¢à¸±à¸à¹à¸¡à¹à¹à¸à¹à¹à¸à¸à¹à¸§à¹à¸à¸µà¹à¹à¸«à¸à¹à¸¥à¸¢ â sign in à¸à¸£à¸µà¹à¸à¸·à¹à¸à¹à¸à¹à¸à¹à¸§à¹', 41 bn: '{noun}à¸à¸à¸à¸à¸¸à¸à¸à¸¢à¸¹à¹à¹à¸à¹à¹à¸à¹à¸à¸£à¸·à¹à¸à¸à¸à¸µà¹ â sign in à¸à¸£à¸µ à¸à¸±à¸à¹à¸¡à¹à¹à¸«à¹à¸«à¸²à¸¢', 42 bnBtn: 'Sign in à¸à¸£à¸µ', 43 migrated: 'âï¸ à¹à¸à¸{noun}à¸à¸µà¹à¸à¸±à¹à¸à¹à¸§à¹à¹à¸à¹à¸à¸£à¸·à¹à¸à¸à¸à¸µà¹ â à¹à¸à¸à¹à¸à¹à¸²à¸à¸±à¸à¸à¸µà¹à¸«à¹à¹à¸¥à¹à¸§', 44 meterFree: 'à¹à¸à¸à¹à¸¥à¹à¸§ {n}/3 · Pro à¹à¸¡à¹à¸à¸³à¸à¸±à¸', meterFull: 'à¸à¸£à¸ 3/3 â Pro à¹à¸¡à¹à¸à¸³à¸à¸±à¸', meterPro: 'à¹à¸à¸à¹à¸¥à¹à¸§ {n}', 45 boothPrompt: 'มีรูภ{n} รูà¸à¸à¸¢à¸¹à¹à¹à¸à¹à¹à¸à¹à¸à¸£à¸·à¹à¸à¸à¸à¸µà¹ â à¹à¸à¸à¸à¸¹à¹à¹à¸à¸·à¹à¸à¹à¸à¹à¸à¸£à¸¹à¸à¹à¸§à¹', boothPromptBtn: 'à¹à¸à¸à¸à¸¹à¹' 46 }, 47 ja: { 48 nounwheel: 'ãã¤ã¼ã«', noungacha: 'ã¬ãã£', nounclaw: 'ã¯ã¬ã¼ã³ã²ã¼ã ', nounphotobooth: 'ãã©ããã¼ã¹', nounthankyou: 'ãµã³ã¯ã¹ã«ã¼ã', 49 gateTitle: 'ç¡æã§ä¿å',
50 gateSub: 'Googleã§ãµã¤ã³ã¤ã³ãã¦ããã®{noun}ãã¢ã«ã¦ã³ãã«ä¿åãã¾ããã', 51 b1: 'ç¡æã§3æ ã¾ã§ä¿å', b2: 'ã©ã®ç«¯æ«ã§ãéãã â ãã©ã¦ã¶ã¨ä¸ç·ã«æ¶ããªã', 52 b3: 'OBSã»æãé飿ºã¯ãã¨ããè¨å®å¯è½', 53 googleBtn: 'Googleã§ç¶è¡', noCard: 'ç¡æ · ã«ã¼ãä¸è¦', gateSuccess: 'ãµã¤ã³ã¤ã³ãã¾ãã â', 54 bnwheel: 'ãã®ãã¤ã¼ã«ã¯ã¾ã ä¿åããã¦ãã¾ãã â ç¡æãµã¤ã³ã¤ã³ã§ä¿åã§ãã¾ã', 55 bn: 'ãã®{noun}ã¯ãã®ç«¯æ«ã«ããããã¾ãã â ç¡æãµã¤ã³ã¤ã³ã§æ¶ããªãããã«', 56 bnBtn: 'ç¡æã§ãµã¤ã³ã¤ã³', 57 migrated: 'âï¸ ãã®ç«¯æ«ã®{noun}ãè¦ã¤ãã¾ãã â ã¢ã«ã¦ã³ãã«ä¿åæ¸ã¿', 58 meterFree: 'ä¿å {n}/3 · Proã¯ç¡å¶é', meterFull: '3/3 ãã£ã±ã â Proã¯ç¡å¶é', meterPro: 'ä¿å {n}', 59 boothPrompt: 'åç{n}æããã®ç«¯æ«ã®ã¿ã« â ãã¼ã¹ãä¿åãã¦æ®ãã¾ããã', boothPromptBtn: 'ãã¼ã¹ãä¿å' 60 }, 61 ko: { 62 nounwheel: '룰ë ', noungacha: 'ê°ì± ', nounclaw: 'ì¸íë½ê¸°', nounphotobooth: 'í¬í ë¶ì¤', nounthankyou: 'ê°ì¬ ì¹´ë', 63 gateTitle: '무ë£ë¡ ì ì¥', 64 gateSub: 'Googleë¡ ë¡ê·¸ì¸íê³ ì´ {noun}ì(를) ê³ì ì ì ì¥íì¸ì', 65 b1: '무ë£ë¡ 3ê°ê¹ì§ ì ì¥', b2: 'ì´ë 기기ììë ì´ë¦¼ â ë¸ë¼ì°ì ì í¨ê» ì¬ë¼ì§ì§ ìì', 66 b3: 'OBS·íì ì°ëì ëì¤ì ì¤ì ê°ë¥', 67 googleBtn: 'Googleë¡ ê³ìí기', noCard: 'ë¬´ë£ Â· ì¹´ë ë¶íì', gateSuccess: 'ë¡ê·¸ì¸ ìë£ â', 68 bnwheel: 'ì´ ë£°ë ì ìì§ ì ì¥ëì§ ìììµëë¤ â ë¬´ë£ ë¡ê·¸ì¸ì¼ë¡ ë³´ê´íì¸ì', 69 bn: '{noun}ì´(ê°) ì´ ê¸°ê¸°ìë§ ììµëë¤ â ë¬´ë£ ë¡ê·¸ì¸ì¼ë¡ ìì´ë²ë¦¬ì§ ìê² íì¸ì', 70 bnBtn: 'ë¬´ë£ ë¡ê·¸ì¸', 71 migrated: 'âï¸ ì´ ê¸°ê¸°ì ì¤ì ë {noun}ì(를) ì°¾ììµëë¤ â ê³ì ì ì ì¥íìµëë¤', 72 meterFree: 'ì ì¥ {n}/3 · Pro 무ì í', meterFull: '3/3 ê°ë ì°¸ â Pro 무ì í', meterPro: 'ì ì¥ {n}', 73 boothPrompt: 'ì¬ì§ {n}ì¥ì´ ì´ ê¸°ê¸°ìë§ ììµëë¤ â ë¶ì¤ë¥¼ ì ì¥í´ ë³´ê´íì¸ì', boothPromptBtn: 'ë¶ì¤ ì ì¥' 74 } 75 }; 76 function S(k) { return (STR[LANG] && STR[LANG][k]) || STR.en[k] || k; } 77 function fmt(k, m) { 78 var t = S(k); 79 Object.keys(m || {}).forEach(function (x) { t = t.split('{' + x + '}').join(m[x]); }); 80 return t; 81 } 82 function noun(p) { return S('noun' + p); } 83 function track(ev, params) { try { if (window._track) window._track(ev, params || {}); } catch (e) {} } 84 function signedIn() { try { return !!(window.Cloud && Cloud.currentUser); } catch (e) { return false; } } 85 function getFlag(k) { try { return localStorage.getItem('woi_ret_' + k) === '1'; } catch (e) { return true; } } 86 function setFlag(k) { try { localStorage.setItem('woi_ret_' + k, '1'); } catch (e) {} } 87 88 var cssDone = false; 89 function ensureCss() { 90 if (cssDone) return; cssDone = true; 91 var st = document.createElement('style'); 92 st.textContent = 93 '#woiretOv{position:fixed;inset:0;background:rgba(30,10,22,.55);backdrop-filter:blur(3px);z-index:100000;display:flex;align-items:center;justify-content:center;padding:18px}' + 94 '#woiretOv .wr-card{background:#fff;border-radius:20px;max-width:400px;width:100%;padding:28px 26px 22px;text-align:center;box-shadow:0 24px 70px rgba(0,0,0,.35);font-family:inherit;position:relative}' + 95 '#woiretOv .wr-x{position:absolute;top:10px;right:12px;border:0;background:none;font-size:20px;color:#b895a8;cursor:pointer;line-height:1;padding:6px}' + 96 '#woiretOv .wr-emoji{font-size:38px;line-height:1;margin-bottom:8px}' + 97 '#woiretOv h3{margin:0 0 6px;font-size:20px;color:#4a2338}' + 98 '#woiretOv .wr-sub{font-size:14px;color:#8a6478;margin:0 0 14px;line-height:1.45}' + 99 '#woiretOv ul{list-style:none;margin:0 0 16px;padding:0;text-align:left;display:inline-block}' + 100 '#woiretOv li{font-size:13.5px;color:#5d3a4e;margin:5px 0}' + 101 '#woiretOv li::before{content:"\\2713";color:#2fae6b;font-weight:900;margin-right:8px}' + 102 '#woiretOv .wr-g{display:flex;align-items:center;justify-content:center;gap:10px;width:100%;padding:12px 16px;border:1px solid #dcd0d8;border-radius:999px;background:#fff;font-size:15px;font-weight:800;color:#3c4043;cursor:pointer;box-shadow:0 2px 8px rgba(0,0,0,.08);font-family:inherit}' + 103 '#woiretOv .wr-g:hover{background:#f8f5f7}' + 104 '#woiretOv .wr-note{font-size:12px;color:#a98a9c;margin-top:10px}' + 105 '#woiretBn{position:fixed;left:50%;bottom:14px;transform:translateX(-50%);z-index:99990;background:linear-gradient(120deg,#fff,#fff6fa);border:1.5px solid #f0b9dc;border-radius:16px;box-shadow:0 10px 34px rgba(160,60,110,.25);padding:12px 16px;display:flex;gap:12px;align-items:center;max-width:min(94vw,560px);font-family:inherit}' + 106 '#woiretBn .wb-t{font-size:13.5px;color:#5d3a4e;line-height:1.4;text-align:left}' + 107 '#woiretBn .wb-b{flex:none;background:linear-gradient(120deg,#e05080,#ff7ba6);color:#fff;border:0;border-radius:999px;padding:9px 16px;font-size:13px;font-weight:900;cursor:pointer;white-space:nowrap;font-family:inherit}' + 108 '#woiretBn .wb-x{flex:none;border:0;background:none;color:#b895a8;font-size:18px;
108cursor:pointer;padding:4px;min-width:40px;min-height:40px}' + 109 '.woiret-meter{display:inline-block;margin:6px 0 0;padding:3px 11px;border-radius:999px;font-size:11.5px;font-weight:800;background:#f6ecf2;color:#8a6478;border:1px solid #eddce6}' + 110 '.woiret-meter.full{background:#fff1e4;color:#b25a00;border-color:#f4cba0;cursor:pointer}' + 111 '#woiretToast{position:fixed;left:50%;top:18px;transform:translateX(-50%);z-index:100001;background:rgba(40,16,30,.94);color:#fff;padding:11px 20px;border-radius:999px;font-size:14px;font-weight:700;box-shadow:0 8px 26px rgba(0,0,0,.35);max-width:92vw}'; 112 document.head.appendChild(st); 113 } 114 115 function toast(msg, ms) { 116 ensureCss(); 117 var t = document.getElementById('woiretToast'); 118 if (!t) { t = document.createElement('div'); t.id = 'woiretToast'; document.body.appendChild(t); } 119 t.textContent = msg; t.style.display = 'block'; 120 clearTimeout(t._to); t._to = setTimeout(function () { t.style.display = 'none'; }, ms || 4200); 121 } 122 123 var G_SVG = '<svg width="19" height="19" viewBox="0 0 48 48" aria-hidden="true"><path fill="#FFC107" d="M43.6 20.1H42V20H24v8h11.3C33.7 32.7 29.3 36 24 36c-6.6 0-12-5.4-12-12s5.4-12 12-12c3.1 0 5.9 1.2 8 3l5.7-5.7C34.3 6.1 29.4 4 24 4 13 4 4 13 4 24s9 20 20 20 20-9 20-20c0-1.3-.1-2.6-.4-3.9z"/><path fill="#FF3D00" d="M6.3 14.7l6.6 4.8C14.7 15.1 19 12 24 12c3.1 0 5.9 1.2 8 3l5.7-5.7C34.3 6.1 29.4 4 24 4 16.3 4 9.7 8.3 6.3 14.7z"/><path fill="#4CAF50" d="M24 44c5.2 0 9.9-2 13.4-5.2l-6.2-5.2C29.2 35.1 26.7 36 24 36c-5.2 0-9.6-3.3-11.3-8l-6.5 5C9.5 39.6 16.2 44 24 44z"/><path fill="#1976D2" d="M43.6 20.1H42V20H24v8h11.3c-.8 2.3-2.3 4.3-4.1 5.7l6.2 5.2C36.9 40.4 44 35 44 24c0-1.3-.1-2.6-.4-3.9z"/></svg>'; 124 var EMOJI = { wheel: 'ð¡', gacha: 'ð°', claw: 'ð§¸', photobooth: 'ð¸' }; 125 126 function closeGate() { var ov = document.getElementById('woiretOv'); if (ov) ov.remove(); } 127 128 function gate(product, from) { 129 if (MUTED) return; 130 ensureCss(); closeGate(); 131 var ov = document.createElement('div'); ov.id = 'woiretOv'; 132 ov.innerHTML = 133 '<div class="wr-card" role="dialog" aria-modal="true">' + 134 '<button class="wr-x" type="button" aria-label="close">' + '<svg class="wi" aria-hidden="true"><use href="/img/wi.svg?v=20260905-1#wi-close"></use></svg>' + '</button>' + 135 '<div class="wr-emoji">' + (EMOJI[product] || 'â¨') + '</div>' + 136 '<h3>' + S('gateTitle') + '</h3>' + 137 '<p class="wr-sub">' + fmt('gateSub', { noun: noun(product) }) + '</p>' + 138 '<ul><li>' + S('b1') + '</li><li>' + S('b2') + '</li><li>' + S('b3') + '</li></ul>' + 139 '<button class="wr-g" type="button">' + G_SVG + '<span>' + S('googleBtn') + '</span></button>' + 140 '<div class="wr-note">' + S('noCard') + '</div>' + 141 '</div>'; 142 document.body.appendChild(ov); 143 track('signin_gate_shown', { product: product, from: from || 'save' }); 144 ov.addEventListener('click', function (e) { if (e.target === ov) closeGate(); }); 145 ov.querySelector('.wr-x').addEventListener('click', closeGate); 146 ov.querySelector('.wr-g').addEventListener('click', function () { 147 track('signin_gate_click', { product: product }); 148 var btn = ov.querySelector('.wr-g'); btn.disabled = true; btn.style.opacity = '.6'; 149 if (!(window.Cloud && Cloud.signInGoogle)) { location.href = (LANG === 'en' ? '' : '/' + LANG) + '/dashboard.html'; return; } 150 // signInGoogle already falls back to a full-page redirect when the popup is blocked. 151 Cloud.signInGoogle().then(function (res) { 152 // null = the visitor closed the Google window (2026-09-28): keep the gate, re-enable the button. 153 if (!res || !res.user) { btn.disabled = false; btn.style.opacity = ''; track('signin_gate_cancel', { product: product }); return; } 154 closeGate(); toast(S('gateSuccess')); 155 track('signin_gate_success', { product: product }); 156 }).catch(function () { btn.disabled = false; btn.style.opacity = ''; }); 157 }); 158 } 159 160 161 // The banner is pinned 14px from the bottom, which on a phone lands exactly on top of a 162 // bottom-fixed product bar (the wheel page's .tabs, the mobile dock on the other pages). 163 // Measured live 2026-09-02 on a 375x667 viewport: it covered 42 of the 56px bar and
164 // elementFromPoint returned the banner for 4 of the 6 product tabs â they could not be 165 // tapped at all while it showed. Measure whatever bottom bar is actually there and sit 166 // above it, so this keeps working if a page changes its dock height. 167 function liftAboveBottomBar(bn) { 168 try { 169 var vh = document.documentElement.clientHeight || window.innerHeight, best = 0; 170 var nodes = document.body.querySelectorAll('*'); 171 for (var i = 0; i < nodes.length; i++) { 172 var e = nodes[i]; 173 if (e === bn || bn.contains(e)) continue; 174 var cs = getComputedStyle(e); 175 if (cs.position !== 'fixed' || cs.display === 'none' || cs.visibility === 'hidden') continue; 176 if (parseFloat(cs.opacity || '1') < 0.05 || cs.pointerEvents === 'none') continue; 177 var r = e.getBoundingClientRect(); 178 if (r.height < 8 || r.width < 60) continue; 179 if (Math.abs(r.bottom - vh) > 2) continue; // anchored to the bottom edge 180 if (r.height > vh * 0.45) continue; // a full-screen overlay is not a bar 181 if (r.height > best) best = r.height; 182 } 183 if (best > 0) bn.style.bottom = Math.round(best + 12) + 'px'; 184 } catch (e) {} 185 } 186 187 // "You'll lose this" banner â fires on the 2nd real use by a signed-out visitor. 188 function showBanner(product, text, btnText, onClick) { 189 if (MUTED || document.getElementById('woiretBn')) return; 190 ensureCss(); 191 var bn = document.createElement('div'); bn.id = 'woiretBn'; 192 bn.innerHTML = '<div class="wb-t"></div><button class="wb-b" type="button"></button><button class="wb-x" type="button" aria-label="dismiss">â</button>'; 193 bn.querySelector('.wb-t').textContent = text; 194 bn.querySelector('.wb-b').textContent = btnText; 195 document.body.appendChild(bn); 196 liftAboveBottomBar(bn); 197 track('keep_banner_shown', { product: product }); 198 bn.querySelector('.wb-x').addEventListener('click', function () { setFlag('bn_' + product); bn.remove(); }); 199 bn.querySelector('.wb-b').addEventListener('click', function () { 200 track('keep_banner_click', { product: product }); 201 bn.remove(); 202 if (onClick) onClick(); else gate(product, 'banner'); 203 }); 204 } 205 206 function use(product) { 207 if (MUTED || signedIn() || getFlag('bn_' + product)) return; 208 var n = 0; 209 try { 210 n = (parseInt(localStorage.getItem('woi_ret_use_' + product), 10) || 0) + 1; 211 localStorage.setItem('woi_ret_use_' + product, String(n)); 212 } catch (e) { return; } 213 var sess; 214 try { sess = sessionStorage.getItem('woi_ret_bns_' + product) === '1'; } catch (e) { sess = true; } 215 if (n >= 2 && !sess) { 216 try { sessionStorage.setItem('woi_ret_bns_' + product, '1'); } catch (e) {} 217 var text = product === 'wheel' ? S('bnwheel') : fmt('bn', { noun: noun(product) }); 218 showBanner(product, text, S('bnBtn')); 219 } 220 } 221 222 // Slot meter chip, rendered right after each page's template picker / list host. 223 function meter(target, n, isPro) { 224 if (MUTED) return; 225 ensureCss(); 226 var host = typeof target === 'string' ? document.querySelector(target) : target; 227 if (!host || !host.parentNode) return; 228 var isSelect = host.tagName === 'SELECT' || host.tagName === 'INPUT'; 229 var scope = isSelect ? host.parentNode : host; 230 var chip = scope.querySelector('.woiret-meter'); 231 if (!chip) { 232 chip = document.createElement('span'); chip.className = 'woiret-meter'; 233 if (isSelect) host.parentNode.insertBefore(chip, host.nextSibling); 234 else host.insertBefore(chip, host.firstChild); 235 } 236 if (isPro) { chip.textContent = fmt('meterPro', { n: n }); chip.classList.remove('full'); chip.onclick = null; return; } 237 if (n >= 3) { 238 chip.textContent = S('meterFull'); chip.classList.add('full'); 239 chip.onclick = function () { location.href = (LANG === 'en' ? '' : '/' + LANG) + '/pricing.html'; }; 240 } else { 241 chip.textContent = fmt('meterFree', { n: n }); chip.classList.remove('full'); chip.onclick = null; 242 } 243 } 244 245 // Photo-booth variant: photos are personal, so nothing is uploaded silently â 246 // the prompt opens the save modal instead (unlike the config-only auto-migrations). 247 function boothPrompt(photoCount, openSave) { 248 if (MUTED || getFlag('bn_photobooth')) return; 249 showBanner('photobooth', fmt('boothPrompt', { n: photoCount }), S('boothPromptBtn'), openSave); 250 } 251 252 function migratedToast(product) { toast(fmt('migrated', { noun: noun(product) })); track('local_migrated', { product: product }); } 253 254 window.WOIRET = { gate: gate, use: use, meter: meter, toast: toast, migratedToast: migratedToast, 255 boothPrompt: boothPrompt, flag: getFlag, setFlag: setFlag, str: S, fmt: fmt, muted: MUTED }; 256})();
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.