1(function () { 2 3 // Trigger hash â append /#/insider-portal to any URL on the site 4 var HASH = '/insider-portal'; 5 6 function shouldOpen() { 7 var h = window.location.hash || ''; 8 // Matches #/insider-portal and the booking-gateway variant #/insider-portal-book 9 return h.indexOf('#/' + HASH.replace(/^\//, '')) === 0 || 10 window.location.href.indexOf('/#' + HASH) !== -1; 11 } 12 13 // ââ Auto-click Ghost portal tier after upgrade navigation ââââââââââââ 14 // This must run on EVERY page load (before any shouldOpen check) 15 // because the upgrade navigates to #/portal/account/plans (full reload) 16 function autoClickGhostTier() { 17 var targetTier = sessionStorage.getItem('ip_upgrade_tier'); 18 if (!targetTier) return; 19 sessionStorage.removeItem('ip_upgrade_tier'); 20 21 var attempts = 0; 22 var maxAttempts = 60; // 6 seconds 23 24 var poll = setInterval(function () { 25 attempts++; 26 if (attempts > maxAttempts) { 27 clearInterval(poll); 28 return; 29 } 30 31 var portalFrame = document.querySelector('iframe[data-testid="portal-popup-frame"]'); 32 if (!portalFrame) { 33 return; 34 } 35 36 var doc; 37 try { 38 doc = portalFrame.contentDocument || (portalFrame.contentWindow && portalFrame.contentWindow.document); 39 } catch(e) { 40 clearInterval(poll); 41 return; 42 } 43 44 45 var allCards = doc.querySelectorAll('.gh-portal-product-card'); 46 47 var cards = doc.querySelectorAll('.gh-portal-product-card:not(.disabled):not(.checked)'); 48 if (!cards.length) return; 49 50 var matched = false; 51 cards.forEach(function (card) { 52 var nameEl = card.querySelector('.gh-portal-product-name'); 53 if (nameEl && nameEl.textContent.trim().toLowerCase() === targetTier.toLowerCase()) { 54 var btn = card.querySelector('button[data-test-button="select-tier"]'); 55 if (btn) { 56 btn.click(); 57 matched = true; 58 } 59 } 60 }); 61 62 if (matched) clearInterval(poll); 63 }, 100); 64 } 65 66 // Run auto-click check immediately on every page load 67 if (sessionStorage.getItem('ip_upgrade_tier')) { 68 autoClickGhostTier(); 69 } 70 71 function initPortal() { 72 // Guard â don't double-init 73 if (document.getElementById('ip-overlay')) return; 74 75 // ââ Tier config âââââââââââââââââââââââââââââââââââââââââââââââââââââ 76 var IP_PRICES = { 77 fan: { monthly: '$10', yearly: '$110' }, 78 insider: { monthly: '$20', yearly: '$220' }, 79 explorer: { monthly: '$30', yearly: '$330' } 80 }; 81 82 // Signup URLs (not logged in) 83 var IP_SIGNUP_URLS = { 84 fan: { monthly: 'https://www.untappedcities.com/#/portal/signup/65b172b5953b8b0008a3f2a4/monthly', yearly: 'https://www.untappedcities.com/#/portal/signup/65b172b5953b8b0008a3f2a4/yearly' }, 85 insider: { monthly: 'https://www.untappedcities.com/#/portal/signup/65b3220dee57060001800397/monthly', yearly: 'https://www.untappedcities.com/#/portal/signup/65b3220dee57060001800397/yearly' }, 86 explorer: { monthly: 'https://www.untappedcities.com/#/portal/signup/65b32254ee5706000180039d/monthly', yearly: 'https://www.untappedcities.com/#/portal/signup/65b32254ee5706000180039d/yearly' } 87 }; 88 89 // Upgrade URL (logged in) â Ghost's native plan change flow 90 // For logged-in members we test using the same signup URLs â Ghost may route to Stripe upgrade directly 91 var IP_UPGRADE_URL = null; // signals to use IP_SIGNUP_URLS[tier][billing] same as non-members 92 93 var TIER_DATA = [ 94 { 95 id: 'fan', 96 name: 'Fan', 97 featured: false, 98 benefits: [ 99 'Access to 12,000+ full articles, ad-free', 100 'Commenting rights', 101 'Stream 400+ on-demand videos', 102 'Special offers from our NYC partners' 103 ] 104 }, 105 { 106 id: 'insider', 107 name: 'Insider', 108 featured: true, 109 badge: 'Most popular', 110 includes: 'All Fan benefits, plus:', 111 benefits: [ 112 'FREE access to our best-selling walking tours (a $42 value per ticket)', 113 'Exclusive members-only in-person experiences', 114 'Members-only tour discounts' 115 ] 116 }, 117 { 118 id: 'explorer', 119 name: 'Explorer', 120 featured: false, 121 includes: 'All Insider benefits, plus:', 122 benefits: [ 123 'Bring a friend to any in-person member experience' 124 ] 125 } 126 ]; 127 128 // ââ Styles ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 129 var style = document.createElement('style'); 130 style.textContent = 131 '#ip-overlay{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.72);z-index:99999;align-items:center;justify-content:center;padding:1rem;box-sizing:border-box;}' + 132 '#ip-overlay.ip-open{display:flex;}' + 133 '#ip-portal{background:var(--color-bg);border-radius:20px;width:100%;max-width:700px;padding:2rem 1.75rem;position:relative;max-height:90vh;overflow-y:auto;font-family:var(--font-body);}' + 134 '#ip-close{position:absolute;top:14px;right:16px;background:none;border:none;font-size:22px;
134cursor:pointer;color:var(--color-text-acc);line-height:1;}' + 135 '#ip-close:hover{color:var(--color-text);}' + 136 '.ip-head{text-align:center;margin-bottom:0;}' + 137 '.ip-headline{font-size:20px;font-weight:700;color:var(--color-text);margin-bottom:6px;font-family:var(--font-headings);}' + 138 '.ip-subhead{font-size:13px;color:var(--color-text-acc);margin-bottom:0;}' + 139 '.ip-subhead a{color:#e1115f;font-weight:600;text-decoration:none;}' + 140 '.ip-billing{display:flex;margin:1.1rem auto 0;max-width:280px;border:1px solid var(--color-border);border-radius:6px;overflow:hidden;}' + 141 '.ip-bill-opt{flex:1;padding:9px 12px;text-align:center;font-size:13px;font-weight:600;cursor:pointer;background:var(--color-bg-acc);color:var(--color-text-acc);border:none;font-family:var(--font-body);transition:background 0.15s,color 0.15s;}' + 142 '.ip-bill-opt.ip-bill-active{background:#e1115f;color:#fff;}' + 143 '.ip-save{font-size:10px;font-weight:400;opacity:0.9;}' + 144 '.ip-tiers{display:grid;gap:14px;margin-top:1.1rem;}' + 145 '.ip-tiers.ip-tiers-3{grid-template-columns:repeat(3,minmax(0,1fr));}' + 146 '.ip-tiers.ip-tiers-2{grid-template-columns:repeat(2,minmax(0,1fr));max-width:480px;margin-left:auto;margin-right:auto;}' + 147 '@media(max-width:580px){.ip-tiers.ip-tiers-3,.ip-tiers.ip-tiers-2{grid-template-columns:1fr;}.ip-tier.ip-featured{order:-1;}}' + 148 '.ip-tier{border:1px solid var(--color-border);border-radius:14px;padding:18px 16px;display:flex;flex-direction:column;position:relative;cursor:pointer;}' + 149 '.ip-tier.ip-featured{border:2px solid #e1115f;}' + 150 '.ip-badge{position:absolute;top:-11px;left:50%;transform:translateX(-50%);background:#e1115f;color:#fff;font-size:10px;font-weight:700;padding:3px 12px;border-radius:20px;white-space:nowrap;}' + 151 '.ip-tier-name{font-size:14px;font-weight:700;color:#e1115f;margin-bottom:6px;font-family:var(--font-headings);}' + 152 '.ip-price{display:flex;align-items:baseline;gap:4px;margin-bottom:2px;}' + 153 '.ip-price-amount{font-size:26px;font-weight:800;color:var(--color-text);line-height:1;font-family:var(--font-headings);}' + 154 '.ip-price-cadence{font-size:13px;font-weight:400;color:var(--color-text-acc);}' + 155 '.ip-includes{font-size:11px;font-weight:600;color:var(--color-text-acc);margin:12px 0 6px;}' + 156 '.ip-benefits{list-style:none;flex:1;padding:0;margin:8px 0 0;}' + 157 '.ip-benefits li{font-size:12px;color:var(--color-text-acc);padding:3px 0;display:flex;align-items:flex-start;gap:8px;line-height:1.5;}' + 158 '.ip-benefits li::before{content:"";flex-shrink:0;margin-top:4px;width:13px;height:9px;background-image:url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 14 10\'%3E%3Cpath d=\'M1 5l4 4L13 1\' stroke=\'%23e1115f\' stroke-width=\'1.8\' fill=\'none\' stroke-linecap=\'round\' stroke-linejoin=\'round\'/%3E%3C/svg%3E");background-repeat:no-repeat;background-size:13px;}' + 159 '.ip-choose{width:100%;margin-top:auto;padding:11px;background:#e1115f !important;color:#fff !important;border:none;border-radius:9px;font-size:14px;font-weight:700;cursor:pointer;font-family:var(--font-body);transition:background 0.15s;}' + 160 '.ip-choose:hover{background:#c40e52 !important;}' + 161 '.ip-terms{margin-top:1.1rem;padding-top:1rem;border-top:1px solid var(--color-border);width:100%;}' + 162 '.ip-terms-label{display:flex;align-items:flex-start;gap:10px;cursor:pointer;font-size:12px;color:var(--color-text-acc);line-height:1.5;}' + 163 '.ip-terms-label input[type="checkbox"]{margin-top:2px;width:16px;height:16px;flex-shrink:0;accent-color:#e1115f;cursor:pointer;}' + 164 '.ip-terms-label a{color:#e1115f;font-weight:600;text-decoration:none;}' + 165 '#ip-tc-warning{display:none;font-size:11px;color:#e1115f;margin:6px 0 0 26px;}' + 166 // Already-member state 167 '.ip-already-state{text-align:center;padding:1.5rem 1rem;}' + 168 '.ip-already-icon{font-size:42px;margin-bottom:12px;}' + 169 '.ip-already-title{font-size:20px;font-weight:700;color:var(--color-text);margin-bottom:8px;font-family:var(--font-headings);}' + 170 '.ip-already-msg{font-size:14px;color:var(--color-text-acc);margin-bottom:1.5rem;line-height:1.6;}' + 171 '.ip-already-btn{display:inline-block;padding:12px 28px;background:#e1115f;color:#fff;border-radius:9px;font-size:14px;font-weight:700;text-decoration:none;font-family:var(--font-body);}' + 172 '.ip-already-btn:hover{background:#c40e52;color:#fff;}'; 173 document.head.appendChild(style); 174 175 // ââ Helpers ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 176 var ipCurrentBilling = 'monthly'; 177 var ipIsUpgrade = false; // true when member is logged in and needs to upgrade 178 var ipVisibleTiers = ['fan', 'insider', 'explorer']; 179 180 function buildTierHTML(tierIds, ctaLabel) { 181 var count = tierIds.length; 182 var html = '<div class="ip-tiers ip-tiers-' + count + '">'; 183 tierIds.forEach(function (id) { 184 var t = TIER_DATA.filter(function (x) { return x.id === id; })[0]; 185 html += '<div class="ip-tier' + (t.featured ? ' ip-featured' : '') + '" onclick="ipSelect(\'' + id + '\')">'; 186 if (t.badge) html += '<div class="ip-badge">' + t.badge + '</div>'; 187 html += '<div class="ip-tier-name">' + t.name + '</div>'; 188 html += '<div class="ip-price"><span class="ip-price-amount" id="ip-price-' + id + '">$' + (id === 'fan' ? '10' : id === 'insider' ? '20' : '30') + '</span><span class="ip-price-cadence" id="ip-cadence-' + id + '">/month</span></div>'; 189 if (t.includes) html += '<div class="ip-includes">' + t.includes + '</div>'; 190 html += '<ul class="ip-benefits">';
191 t.benefits.forEach(function (b) { html += '<li>' + b + '</li>'; }); 192 html += '</ul>'; 193 html += '<button class="ip-choose">' + ctaLabel + '</button>'; 194 html += '</div>'; 195 }); 196 html += '</div>'; 197 return html; 198 } 199 200 function buildSignupModal(tierIds, headline, subhead, ctaLabel) { 201 return '<div id="ip-overlay" class="ip-open">' + 202 '<div id="ip-portal">' + 203 '<button id="ip-close" onclick="ipClose()" aria-label="Close">×</button>' + 204 '<div class="ip-head">' + 205 '<div class="ip-headline">' + headline + '</div>' + 206 '<p class="ip-subhead">' + subhead + '</p>' + 207 '<div class="ip-billing">' + 208 '<button class="ip-bill-opt ip-bill-active" id="ip-opt-monthly" onclick="ipBilling(\'monthly\')" type="button">Monthly</button>' + 209 '<button class="ip-bill-opt" id="ip-opt-yearly" onclick="ipBilling(\'yearly\')" type="button">Yearly <span class="ip-save">(1 month free)</span></button>' + 210 '</div>' + 211 '</div>' + 212 buildTierHTML(tierIds, ctaLabel) + 213 '<div class="ip-terms">' + 214 '<label class="ip-terms-label">' + 215 '<input type="checkbox" id="ip-tc" onchange="ipTcChange()">' + 216 '<span>Automatic renewal. Cancel anytime. By joining, you agree to Untapped New York\'s <a href="https://www.untappedcities.com/terms-of-use/" target="_blank">Terms & Conditions</a>.</span>' + 217 '</label>' + 218 '<p id="ip-tc-warning">Please agree to the Terms & Conditions to continue.</p>' + 219 '</div>' + 220 '</div>' + 221 '</div>'; 222 } 223 224 function buildAlreadyModal(tierName) { 225 return '<div id="ip-overlay" class="ip-open">' + 226 '<div id="ip-portal">' + 227 '<button id="ip-close" onclick="ipClose()" aria-label="Close">×</button>' + 228 '<div class="ip-already-state">' + 229 '<div class="ip-already-icon">â</div>' + 230 '<div class="ip-already-title">You\'re already ' + (tierName === 'Explorer' ? 'an Explorer' : 'an Insider') + '!</div>' + 231 '<p class="ip-already-msg">You already have access to all Insider member events and benefits.<br>Head to the members area to see what\'s coming up.</p>' + 232 '<a href="https://www.untappedcities.com/insiders/events/" class="ip-already-btn">View Member Events â</a>' + 233 '</div>' + 234 '</div>' + 235 '</div>'; 236 } 237 238 // ââ Member detection âââââââââââââââââââââââââââââââââââââââââââââââââ 239 // Membership state comes from assets/js/member.js. This used to read 240 // subscriptions[0] with no status check, so a member who had upgraded could 241 // be identified from their old cancelled subscription, and comped members 242 // were always treated as free. 243 function getMember() { 244 if (!window.UNYMember) return Promise.resolve(null); 245 return window.UNYMember.get(); 246 } 247 248 function getTierSlug(m) { 249 if (!m || !m.signedIn) return null; 250 if (!m.tier) return 'free'; 251 var name = m.tier.toLowerCase(); 252 if (name.indexOf('superfan') !== -1) return 'superfan'; 253 if (name.indexOf('explorer') !== -1) return 'explorer'; 254 if (name.indexOf('insider') !== -1) return 'insider'; 255 if (name.indexOf('fan') !== -1) return 'fan'; 256 if (name.indexOf('supporter') !== -1) return 'supporter'; 257 return 'free'; 258 } 259 260 // ââ Render based on member state âââââââââââââââââââââââââââââââââââââ 261 getMember().then(function (member) { 262 var tierSlug = getTierSlug(member); 263 var signedIn = !!(member && member.signedIn); 264 var modalHTML; 265 // The booking gateway opens this portal via #/insider-portal-book to steer 266 // prospective members to the tour-eligible tiers only. The normal 267 // #/insider-portal (header, events page) is unaffected. 268 var bookContext = window.location.href.indexOf('insider-portal-book') !== -1; 269 270 if (!signedIn) { 271 // State 1: Not logged in â sign up modal 272 ipIsUpgrade = false;
273 ipVisibleTiers = bookContext ? ['insider', 'explorer'] : ['fan', 'insider', 'explorer']; 274 modalHTML = buildSignupModal( 275 ipVisibleTiers, 276 'Unlock Free Tours as an Insider', 277 'Already a member? <a href="https://www.untappedcities.com/#/portal/signin">Sign in</a>', 278 'Choose' 279 ); 280 281 } else if (tierSlug === 'insider' || tierSlug === 'explorer' || tierSlug === 'superfan') { 282 // State 5: Already at Insider or above â "you're all set" modal 283 var displayName = tierSlug.charAt(0).toUpperCase() + tierSlug.slice(1); 284 modalHTML = buildAlreadyModal(displayName); 285 286 } else if (tierSlug === 'fan') { 287 // State 4: Logged in as Fan â show Insider + Explorer upgrade only 288 ipIsUpgrade = true; 289 ipVisibleTiers = ['insider', 'explorer']; 290 modalHTML = buildSignupModal( 291 ipVisibleTiers, 292 'Upgrade to Unlock Free Tours', 293 'Upgrade your Fan membership to get access to exclusive member events and free tours.', 294 'Upgrade' 295 ); 296 297 } else { 298 // States 2 & 3: Free subscriber or Supporter â upgrade modal 299 ipIsUpgrade = true; 300 ipVisibleTiers = bookContext ? ['insider', 'explorer'] : ['fan', 'insider', 'explorer']; 301 modalHTML = buildSignupModal( 302 ipVisibleTiers, 303 'Upgrade Your Membership', 304 'Choose a tier to unlock member events, free tours, and more.', 305 'Upgrade' 306 ); 307 } 308 309 document.body.insertAdjacentHTML('beforeend', modalHTML); 310 311 // Close on overlay background click 312 document.getElementById('ip-overlay').addEventListener('click', function (e) { 313 if (e.target === this) ipClose(); 314 }); 315 }); 316 317 // ââ Controls âââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 318 window.ipClose = function () { 319 var overlay = document.getElementById('ip-overlay'); 320 if (overlay) overlay.classList.remove('ip-open'); 321 history.replaceState(null, '', window.location.pathname + window.location.search); 322 }; 323 324 window.ipBilling = function (mode) { 325 ipCurrentBilling = mode; 326 var monthly = document.getElementById('ip-opt-monthly'); 327 var yearly = document.getElementById('ip-opt-yearly'); 328 if (monthly) monthly.classList.toggle('ip-bill-active', mode === 'monthly'); 329 if (yearly) yearly.classList.toggle('ip-bill-active', mode === 'yearly'); 330 var cadence = mode === 'yearly' ? '/year' : '/month'; 331 ipVisibleTiers.forEach(function (tier) { 332 var priceEl = document.getElementById('ip-price-' + tier); 333 var cadenceEl = document.getElementById('ip-cadence-' + tier); 334 if (priceEl) priceEl.textContent = IP_PRICES[tier][mode]; 335 if (cadenceEl) cadenceEl.textContent = cadence; 336 }); 337 }; 338 339 window.ipTcChange = function () { 340 var warning = document.getElementById('ip-tc-warning'); 341 if (warning) warning.style.display = 'none'; 342 }; 343 344 window.ipSelect = function (tier) { 345 var tc = document.getElementById('ip-tc'); 346 if (tc && !tc.checked) { 347 var warning = document.getElementById('ip-tc-warning'); 348 if (warning) warning.style.display = 'block'; 349 return; 350 } 351 var btns = document.querySelectorAll('.ip-choose'); 352 btns.forEach(function (b) { b.disabled = true; b.style.opacity = '0.7'; }); 353 var idx = ipVisibleTiers.indexOf(tier); 354 if (idx !== -1 && btns[idx]) btns[idx].textContent = 'Redirecting\u2026'; 355 356 if (ipIsUpgrade) { 357 var tierName = tier.charAt(0).toUpperCase() + tier.slice(1); 358 sessionStorage.setItem('ip_upgrade_tier', tierName); 359 // Close our modal first 360 var overlay = document.getElementById('ip-overlay'); 361 if (overlay) overlay.classList.remove('ip-open'); 362 // Set hash to open Ghost's plan screen 363 window.location.hash = '#/portal/account/plans'; 364 // Start polling immediately â no page reload, so we call directly 365 autoClickGhostTier(); 366 } else { 367 window.location.href = IP_SIGNUP_URLS[tier][ipCurrentBilling]; 368 } 369 }; 370 371 } // end initPortal() 372 373 // Always listen for hash changes â works when user pastes /#/insider-portal into address bar on existing page 374 window.addEventListener('hashchange', function () { 375 if (shouldOpen() && !document.getElementById('ip-overlay')) { 376 initPortal(); 377 } 378 }); 379 380 // On page load, run if hash already present 381 if (shouldOpen()) { 382 initPortal(); 383 } 384 385 // Check if we should auto-click a Ghost portal tier (runs after upgrade navigation) 386 if (sessionStorage.getItem('ip_upgrade_tier')) { 387 autoClickGhostTier(); 388 } 389 390})();
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.