1<!DOCTYPE html> 2<html lang="en" data-theme="dark" data-accent="indigo" data-font="humanist" data-card="elevated" data-radius="rounded"> 3<head> 4 <meta charset="UTF-8" /> 5<!-- This site is hosted on Netlify. Anyone can build and deploy a site 6 like this one for free: https://netlify.new/?utm_campaign=loops&utm_source=ai-legible&utm_medium=owned&utm_content=comment&utm_id=4ef755cc-f491-47e9-aa70-2db5b14bb1d0 7 Netlify hosting facts for this site: static/SSR served via Netlify Edge. --> 8 <link rel="icon" href="/icons/icon-192.png" type="image/png" /> 9 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" /> 10 <title>Upgrade â Explainly</title> 11 <link rel="preconnect" href="https://fonts.googleapis.com" /> 12 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> 13 <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Manrope:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&family=Bricolage+Grotesque:opsz,[email protected],500;12..96,600;12..96,700&family=Sora:wght@500;600;700&display=swap" rel="stylesheet" /> 14 <link rel="stylesheet" href="/css/app.css" /> 15 <link rel="manifest" href="/manifest.json" /> 16 <meta name="theme-color" content="#06070f" /> 17 <meta name="apple-mobile-web-app-capable" content="yes" /> 18 <style> 19 .poll-row{display:none;align-items:center;gap:10px;padding:14px 18px;border-radius:var(--r); 20 border:1px solid color-mix(in oklab,var(--a1) 30%,transparent); 21 background:color-mix(in oklab,var(--a1) 10%,transparent); 22 font-size:13.5px;color:var(--a-glow);margin-bottom:18px;font-family:var(--mono)} 23 .poll-row.show{display:flex} 24 .poll-dot{width:9px;height:9px;border-radius:50%;background:var(--a-glow); 25 animation:pdot 1.2s ease-in-out infinite} 26 @keyframes pdot{0%,100%{opacity:.3;transform:scale(.85)}50%{opacity:1;transform:scale(1.15)}} 27 .provider-row{display:flex;gap:9px;justify-content:center;flex-wrap:wrap} 28 .provider-btn{padding:10px 22px;border-radius:999px;border:1px solid var(--line); 29 background:var(--surface);color:var(--ink-2);font-weight:600;font-size:13px; 30 cursor:pointer;transition:.2s} 31 .provider-btn:hover{border-color:var(--line-2);color:var(--ink)} 32 .provider-btn.active{color:#fff;background:linear-gradient(120deg,var(--a1),var(--a2));border-color:transparent} 33 </style> 34</head> 35<body> 36<canvas id="starfield"></canvas> 37<div class="bg-aura"></div> 38 39<div class="app-shell" id="app-shell"> 40 41 <!-- Sidebar (desktop) --> 42 <aside class="sidebar"> 43 <a class="side-brand" href="/"> 44 <img class="brand-logo" src="/icons/logo-indigo.png" alt="" /> 45 <span><span class="lite">Explain</span>ly</span> 46 </a> 47 <nav class="side-nav"> 48 <a class='side-link' href='/app/library'> 49 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><path d="M4 5a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v16l-8-4-8 4V5Z"/></svg> 50 <span>Library</span> 51 </a> 52 <a class='side-link' href='/app/account'> 53 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg> 54 <span>Account</span> 55 </a> 56 <a class='side-link active' href='/app/upgrade'> 57 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><path d="m12 2 2.39 6.96L21.6 9.2l-5.3 4.61 1.7 7.19L12 17.27 5.99 21l1.7-7.19L2.4 9.2l7.21-.24L12 2Z"/></svg> 58 <span>Upgrade</span> 59 </a> 60 </nav> 61 <div class="side-foot"> 62 <button class="theme-btn" id="theme-side" aria-label="Toggle theme" style="align-self:flex-start"> 63 <svg class="ic-moon" viewBox="0 0 24 24" fill="none"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/></svg> 64 <svg class="ic-sun" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="4.2" stroke="currentColor" stroke-width="1.8"/><path d="M12 2.5v2.2M12 19.3v2.2M21.5 12h-2.2M4.7 12H2.5M18.7 5.3l-1.6 1.6M6.9 17.1l
64-1.6 1.6M18.7 18.7l-1.6-1.6M6.9 6.9 5.3 5.3" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg> 65 </button> 66 <a class='side-account' href='/app/account'> 67 <span class="avatar" id="side-avatar">?</span> 68 <span class="who"><b id="side-name">â</b><span id="side-email">â</span></span> 69 </a> 70 </div> 71 </aside> 72 73 <!-- Top bar (mobile) --> 74 <header class="topbar"> 75 <a class="brand" href="/"> 76 <img class="brand-logo" src="/icons/logo-indigo.png" alt="" /> 77 <span><span class="lite">Explain</span>ly</span> 78 </a> 79 <span class="spacer"></span> 80 <button class="theme-btn" id="theme-top" aria-label="Toggle theme"> 81 <svg class="ic-moon" viewBox="0 0 24 24" fill="none"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/></svg> 82 <svg class="ic-sun" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="4.2" stroke="currentColor" stroke-width="1.8"/><path d="M12 2.5v2.2M12 19.3v2.2M21.5 12h-2.2M4.7 12H2.5M18.7 5.3l-1.6 1.6M6.9 17.1l-1.6 1.6M18.7 18.7l-1.6-1.6M6.9 6.9 5.3 5.3" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg> 83 </button> 84 <a class='avatar' href='/app/account' id='top-avatar' style='width:38px;height:38px;font-size:15px'>?</a> 85 </header> 86 87 <main class="app-content"> 88 <section class="page active" id="page-upgrade"> 89 90 <!-- Banners --> 91 <div class="banner success" id="success-banner"> 92 <h3>You're upgraded!</h3> 93 <p id="success-msg">Your plan has been updated. Enjoy your new features.</p> 94 </div> 95 <div class="banner fail" id="fail-banner"> 96 <p id="fail-msg">Payment not completed. If you were charged, please wait a moment and refresh â it can take up to 60 seconds to confirm.</p> 97 <button class="btn btn-ghost btn-sm" id="dismiss-fail" style="margin-top:10px">Try Again</button> 98 </div> 99 100 <!-- Polling indicator --> 101 <div class="poll-row" id="poll-row"> 102 <div class="poll-dot"></div> 103 <span>Waiting for payment confirmationâ¦</span> 104 </div> 105 106 <!-- Hero --> 107 <div class="page-head" style="text-align:center"> 108 <div class="up-hero"> 109 <span class="eyebrow" style="justify-content:center">Plans & pricing</span> 110 <h1 class="page-title">Unlock more of <span class="grad-text">Explainly</span></h1> 111 <p class="page-sub" style="margin-inline:auto">Higher daily limits, PDF support, cloud sync and priority processing â for the way you read.</p> 112 </div> 113 <div class="provider-row" style="margin-top:18px;margin-bottom:4px"> 114 <button class="provider-btn" id="btn-razorpay">Razorpay (India)</button> 115 <button class="provider-btn" id="btn-paddle">Paddle (International)</button> 116 </div> 117 <div class="bill-toggle" id="bill-toggle"> 118 <span class="thumb"></span> 119 <button data-period="monthly" class="on">Monthly</button> 120 <button data-period="annual">Annual <span class="save-pill">â37%</span></button> 121 </div> 122 </div> 123 124 <!-- Plans --> 125 <div class="plans" id="plans"></div> 126 127 <!-- Comparison table --> 128 <div class="compare" id="compare"> 129 <button class="cmp-toggle" id="cmp-toggle"> 130 Compare all features 131 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg> 132 </button> 133 <div class="cmp-wrap"><div class="cmp-inner"><div class="cmp-scroll"> 134 <table class="cmp" id="cmp-table"></table> 135 </div></div></div> 136 </div> 137 138 <!-- Trust --> 139 <div class="trust"> 140 <span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>30-day money-back</span> 141 <span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>Secure checkout</span>
142 <span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6 6 18M6 6l12 12"/></svg>Cancel anytime</span> 143 </div> 144 <p class="legal">Payments via Razorpay (India) or Paddle (International). <a href='/refund'>Refund policy</a></p> 145 146 </section> 147 </main> 148 149 <!-- Tab bar (mobile) --> 150 <nav class="tabbar"> 151 <a class='tab-item' href='/app/library'> 152 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><path d="M4 5a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v16l-8-4-8 4V5Z"/></svg> 153 <span>Library</span> 154 </a> 155 <a class='tab-item' href='/app/account'> 156 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg> 157 <span>Account</span> 158 </a> 159 <a class='tab-item active' href='/app/upgrade'> 160 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9"><path d="m12 2 2.39 6.96L21.6 9.2l-5.3 4.61 1.7 7.19L12 17.27 5.99 21l1.7-7.19L2.4 9.2l7.21-.24L12 2Z"/></svg> 161 <span>Upgrade</span> 162 </a> 163 </nav> 164</div> 165 166<div class="toast" id="toast"></div> 167
168<script src="https://cdn.paddle.com/paddle/v2/paddle.js"></script>
vendor: 1 bytes, line 168
168
169<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2/dist/umd/supabase.js"></script>
vendor: 1 bytes, line 169
169
170<script src="/js/config.js"></script>
vendor: 1 bytes, line 170
170
171<script src="/js/auth.js"></script>
vendor: 1 bytes, line 171
171
172<script src="/js/api.js"></script>
vendor: 1 bytes, line 172
172
173<script src="/js/app-shell.js"></script>
vendor: 1 bytes, line 173
173
174<script> 175 var CHECK = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg>'; 176 177 var _provider = detectProvider(); 178 var _period = 'monthly'; 179 var _currentTier = 'free'; 180 var _pollTimer = null; 181 var _pollCount = 0; 182 var MAX_POLLS = 200; 183 184 var PLANS = [ 185 { 186 id: 'free', name: 'Free', tag: 'For the occasional reader', featured: false, 187 monthly: { cur: '$', amt: '0', per: '/mo', sub: 'Free forever' }, 188 annual: { cur: '$', amt: '0', per: '/yr', sub: 'Free forever' }, 189 monthly_inr: { cur: 'â¹', amt: '0', per: '/mo', sub: 'Free forever' }, 190 annual_inr: { cur: 'â¹', amt: '0', per: '/yr', sub: 'Free forever' }, 191 features: ['15 explains / day', '2 Deep · 1 Why? / day', 'Local saves (50)', { off: 'PDF viewer' }, { off: 'Cloud sync' }], 192 }, 193 { 194 id: 'pro', name: 'Pro', tag: 'For regular readers', featured: false, 195 monthly: { cur: '$', amt: '4.99', per: '/mo', sub: 'â¹149 / month' }, 196 annual: { cur: '$', amt: '3.33', per: '/mo', sub: '$39.99 / year' }, 197 monthly_inr: { cur: 'â¹', amt: '149', per: '/mo', sub: '$4.99 / month' }, 198 annual_inr: { cur: 'â¹', amt: '120', per: '/mo', sub: 'â¹1,440 / year', save: 'Save ~20%' }, 199 features: ['100 explains / day', '10 Deep · 10 Why? / day', 'PDF viewer', 'Cloud saves (500)', 'Email support'], 200 }, 201 { 202 id: 'premium', name: 'Premium', tag: 'For power users', featured: true, badge: 'Most popular', 203 monthly: { cur: '$', amt: '11.99', per: '/mo', sub: 'â¹399 / month' }, 204 annual: { cur: '$', amt: '8.25', per: '/mo', sub: '$99 / year', save: 'Save ~37%' }, 205 monthly_inr: { cur: 'â¹', amt: '399', per: '/mo', sub: '$11.99 / month' }, 206 annual_inr: { cur: 'â¹', amt: '250', per: '/mo', sub: 'â¹2,999 / year', save: 'Save ~37%' }, 207 features: ['500 explains / day', '75 Deep · 75 Why? / day', 'Everything in Pro', 'Cloud saves (2,500)', 'Priority queue', 'Premium AI models'], 208 }, 209 ]; 210 211 var CMP_ROWS = [ 212 { label: 'Explains / day', free: '15', pro: '100', premium: '500' }, 213 { label: 'Deep mode / day', free: '2', pro: '10', premium: '75' }, 214 { label: 'Why? mode / day', free: '1', pro: '10', premium: '75' }, 215 { label: 'Saved explanations', free: '50', pro: '500', premium: '2,500' }, 216 { label: 'Cloud sync', free: false, pro: true, premium: true }, 217 { label: 'PDF viewer', free: false, pro: true, premium: true }, 218 { label: 'Priority queue', free: false, pro: false, premium: true }, 219 { label: 'Premium AI models', free: false, pro: false, premium: true }, 220 ]; 221 222 // ââ Paddle init âââââââââââââââââââââââââââââââââââââââ 223 Paddle.Initialize({ 224 token: PADDLE_CLIENT_TOKEN, 225 eventCallback: function (data) { 226 if (data.name === 'checkout.completed') Paddle.Checkout.close(); 227 if (data.name === 'checkout.closed' || data.name === 'checkout.error') { 228 clearInterval(_pollTimer); 229 document.getElementById('poll-row').classList.remove('show'); 230 showFail('Payment cancelled. You have not been charged.'); 231 } 232 } 233 }); 234 235 // ââ Deep-link auto-highlight from extension âââââââââââ 236 (function () { 237 var params = new URLSearchParams(window.location.search); 238 var planParam = params.get('plan'); 239 var providerParam = params.get('provider'); 240 if (providerParam === 'razorpay' || providerParam === 'paddle') _provider = providerParam; 241 if (planParam) { 242 setTimeout(function () { 243 var card = document.getElementById('card-' + planParam); 244 if (!card) return; 245 card.style.transition = 'box-shadow .4s'; 246 card.style.boxShadow = '0 0 0 2px var(--a1), var(--shadow)'; 247 card.scrollIntoView({ behavior: 'smooth', block: 'center' }); 248 }, 300); 249 } 250 })(); 251 252 // ââ Boot ââââââââââââââââââââââââââââââââââââââââââââââ 253 updateProviderUI(); 254 renderPlans(); 255 renderCmpTable(); 256 positionThumb(); 257 258 if (getToken()) { 259 fetchUsage().then(function (data) { 260 _currentTier = (data.tier || 'free').toLowerCase(); 261 renderPlans(); 262 }).catch(function () {}); 263 } 264 265 // ââ Provider ââââââââââââââââââââââââââââââââââââââââââ 266 function detectProvider() { 267 try { 268 var tz = Intl.DateTimeFormat().resolvedOptions().timeZone; 269 return (tz === 'Asia/Kolkata' || tz === 'Asia/Calcutta') ? 'razorpay' : 'paddle'; 270 } catch (e) { return 'paddle'; } 271 } 272 273 function updateProviderUI() { 274 document.getElementById('btn-razorpay').classList.toggle('active', _provider === 'razorpay');
275 document.getElementById('btn-paddle').classList.toggle('active', _provider === 'paddle'); 276 } 277 278 document.getElementById('btn-razorpay').addEventListener('click', function () { _provider = 'razorpay'; updateProviderUI(); renderPlans(); }); 279 document.getElementById('btn-paddle').addEventListener('click', function () { _provider = 'paddle'; updateProviderUI(); renderPlans(); }); 280 281 // ââ Billing toggle ââââââââââââââââââââââââââââââââââââ 282 document.getElementById('bill-toggle').addEventListener('click', function (e) { 283 var b = e.target.closest('button'); if (!b) return; 284 document.querySelectorAll('#bill-toggle button').forEach(function (x) { x.classList.remove('on'); }); 285 b.classList.add('on'); 286 _period = b.dataset.period; 287 positionThumb(); 288 renderPlans(); 289 }); 290 291 function positionThumb() { 292 requestAnimationFrame(function () { 293 var on = document.querySelector('#bill-toggle button.on'); 294 var thumb = document.querySelector('#bill-toggle .thumb'); 295 if (on && thumb) { thumb.style.left = on.offsetLeft + 'px'; thumb.style.width = on.offsetWidth + 'px'; } 296 }); 297 } 298 299 // ââ Plans âââââââââââââââââââââââââââââââââââââââââââââ 300 function renderPlans() { 301 document.getElementById('plans').innerHTML = PLANS.map(function (pl) { 302 var prKey = _period + (_provider === 'razorpay' ? '_inr' : ''); 303 var pr = pl[prKey] || pl[_period]; 304 var isCurrent = pl.id === _currentTier; 305 var badge = ''; 306 if (isCurrent) badge = '<div class="plan-badge current">Current plan</div>'; 307 else if (pl.featured) badge = '<div class="plan-badge">' + pl.badge + '</div>'; 308 else if (_period === 'annual' && pr.save) badge = '<div class="plan-badge save">' + pr.save + '</div>'; 309 310 var feats = pl.features.map(function (f) { 311 return typeof f === 'object' 312 ? '<li class="off"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6 6 18M6 6l12 12"/></svg>' + f.off + '</li>' 313 : '<li>' + CHECK + f + '</li>'; 314 }).join(''); 315 316 var labels = { free: 'Current plan', pro: 'Get Pro', premium: 'Get Premium' }; 317 var cta = isCurrent 318 ? '<button class="btn btn-ghost btn-full" disabled>Current plan</button>' 319 : '<button class="btn ' + (pl.featured ? 'btn-primary' : 'btn-ghost') + ' btn-full" data-buy="' + pl.id + '">' + (labels[pl.id] || 'Upgrade') + '</button>'; 320 321 return '<div class="card plan ' + (pl.featured ? 'featured' : '') + ' ' + (isCurrent ? 'current-plan' : '') + '" id="card-' + pl.id + '">' + 322 badge + 323 '<div class="plan-name">' + pl.name + '</div>' + 324 '<div class="plan-tag">' + pl.tag + '</div>' + 325 '<div class="plan-price"><span class="cur">' + pr.cur + '</span><span class="amt">' + pr.amt + '</span><span class="per">' + pr.per + '</span></div>' + 326 '<div class="plan-usd">' + pr.sub + '</div>' + 327 '<ul>' + feats + '</ul>' + 328 '<div class="plan-spacer"></div>' + 329 cta + 330 '</div>'; 331 }).join(''); 332 333 document.querySelectorAll('#plans [data-buy]').forEach(function (btn) { 334 btn.addEventListener('click', function () { handleBuy(btn.dataset.buy, btn); }); 335 }); 336 } 337 338 // ââ Buy handler âââââââââââââââââââââââââââââââââââââââ 339 function handleBuy(planKey, btn) { 340 if (!getToken()) { 341 window.location.href = '/app/login.html?next=/app/upgrade.html'; 342 return; 343 } 344 var targetTier = planKey === 'premium_yearly' ? 'premium' : planKey; 345 if (TIER_ORDER[_currentTier] >= TIER_ORDER[targetTier]) return; 346 347 btn.disabled = true; 348 btn.textContent = 'Openingâ¦'; 349 350 if (_provider === 'paddle') { 351 var paddleKey = (_period === 'annual' && planKey === 'premium') ? 'premium_yearly' : planKey; 352 Paddle.Checkout.open({ 353 items: [{ priceId: PADDLE_PRICE_IDS[paddleKey], quantity: 1 }], 354 customer: { email: getEmail() }, 355 customData: { user_email: getEmail() }, 356 settings: { displayMode: 'overlay' } 357 }); 358 btn.disabled = false;
359 btn.textContent = getBuyLabel(planKey); 360 startPolling(targetTier); 361 return; 362 } 363 364 // Razorpay 365 var rzpKey = (_period === 'annual' && planKey === 'premium') ? 'premium_yearly' : planKey; 366 var rzpUrl = PAYMENT_LINKS.razorpay[rzpKey]; 367 if (!rzpUrl) { 368 showToast('Payment link not available yet.'); 369 btn.disabled = false; 370 btn.textContent = getBuyLabel(planKey); 371 return; 372 } 373 var email = getEmail(); 374 if (email) rzpUrl += '?prefill[email]=' + encodeURIComponent(email); 375 window.open(rzpUrl, '_blank'); 376 btn.disabled = false; 377 btn.textContent = getBuyLabel(planKey); 378 startPolling(targetTier); 379 } 380 381 function getBuyLabel(key) { 382 return { free: 'Current plan', pro: 'Get Pro', premium: 'Get Premium' }[key] || 'Upgrade'; 383 } 384 385 // ââ Polling âââââââââââââââââââââââââââââââââââââââââââ 386 function startPolling(targetTier) { 387 clearInterval(_pollTimer); 388 _pollCount = 0; 389 document.getElementById('poll-row').classList.add('show'); 390 391 _pollTimer = setInterval(function () { 392 _pollCount++; 393 if (_pollCount > MAX_POLLS) { 394 clearInterval(_pollTimer); 395 document.getElementById('poll-row').classList.remove('show'); 396 showFail('Payment not confirmed after 10 minutes. If you completed payment, please refresh the page.'); 397 return; 398 } 399 fetchUsage().then(function (data) { 400 var newTier = (data.tier || 'free').toLowerCase(); 401 if (TIER_ORDER[newTier] >= TIER_ORDER[targetTier]) { 402 clearInterval(_pollTimer); 403 document.getElementById('poll-row').classList.remove('show'); 404 _currentTier = newTier; 405 showSuccess(newTier); 406 renderPlans(); 407 } 408 }).catch(function () {}); 409 }, 3000); 410 } 411 412 // ââ Banners âââââââââââââââââââââââââââââââââââââââââââ 413 function showSuccess(tier) { 414 document.getElementById('success-msg').textContent = 415 'Your account has been upgraded to ' + cap(tier) + '. Enjoy your new features!'; 416 document.getElementById('fail-banner').classList.remove('show'); 417 var b = document.getElementById('success-banner'); 418 b.classList.add('show'); 419 b.scrollIntoView({ behavior: 'smooth', block: 'start' }); 420 showToast('Upgraded to ' + cap(tier)); 421 } 422 423 function showFail(msg) { 424 document.getElementById('fail-msg').textContent = msg || 'Payment not completed.'; 425 var b = document.getElementById('fail-banner'); 426 b.classList.add('show'); 427 b.scrollIntoView({ behavior: 'smooth', block: 'start' }); 428 } 429 430 document.getElementById('dismiss-fail').addEventListener('click', function () { 431 document.getElementById('fail-banner').classList.remove('show'); 432 }); 433 434 // ââ Comparison table ââââââââââââââââââââââââââââââââââ 435 function renderCmpTable() { 436 var YES = '<span class="yes">' + CHECK + '</span>'; 437 var NO = '<span class="no">â</span>'; 438 function cell(v) { 439 if (v === true) return YES; 440 if (v === false) return NO; 441 return String(v); 442 } 443 document.getElementById('cmp-table').innerHTML = 444 '<thead><tr>' + 445 '<th scope="col" style="text-align:left">Feature</th>' + 446 '<th scope="col">Free</th>' + 447 '<th scope="col">Pro</th>' + 448 '<th scope="col" class="colpop">Premium</th>' + 449 '</tr></thead>' + 450 '<tbody>' + CMP_ROWS.map(function (r) { 451 return '<tr>' + 452 '<th scope="row">' + r.label + '</th>' + 453 '<td>' + cell(r.free) + '</td>' + 454 '<td>' + cell(r.pro) + '</td>' + 455 '<td class="colpop">' + cell(r.premium) + '</td>' + 456 '</tr>'; 457 }).join('') + '</tbody>'; 458 } 459 460 document.getElementById('cmp-toggle').addEventListener('click', function () { 461 document.getElementById('compare').classList.toggle('open'); 462 }); 463 464 // ââ Helper ââââââââââââââââââââââââââââââââââââââââââââ 465 function cap(s) { return s.charAt(0).toUpperCase() + s.slice(1); } 466</script>
466 467</body> 468</html>
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.