1<!DOCTYPE html> 2<html lang="en"> 3 <head> 4 <meta charset="utf-8" /> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge" /> 6 <meta name="author" content="jamaljsr" /> 7 <meta 8 name="description" 9 content="Donate to Polar to help builders ship Lightning apps faster. Choose a tier, add your handle (optional), and donate with Lightning or Bitcoin." 10 /> 11 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 12 13 <title>Donate to Polar</title> 14 15 <link 16 rel="shortcut icon" 17 href="../images/favicon.ico" 18 type="image/x-icon" 19 /> 20 <link rel="icon" href="../images/favicon.ico" type="image/x-icon" /> 21 <link rel="apple-touch-icon" href="../images/apple-touch-icon.png" /> 22 23 <link 24 href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,900" 25 rel="stylesheet" 26 /> 27 <link 28 href="https://fonts.googleapis.com/css?family=Noto+Sans+TC:300,400,500,700,900" 29 rel="stylesheet" 30 /> 31 32 <link href="../css/bootstrap.min.css" rel="stylesheet" /> 33 <link 34 href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" 35 integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" 36 rel="stylesheet" 37 crossorigin="anonymous" 38 /> 39 <link href="../css/flaticon.css" rel="stylesheet" /> 40 <link href="../css/magnific-popup.css" rel="stylesheet" /> 41 <link href="../css/owl.carousel.min.css" rel="stylesheet" /> 42 <link href="../css/owl.theme.default.min.css" rel="stylesheet" /> 43 <link href="../css/animate.css" rel="stylesheet" /> 44 <link href="../css/main.css" rel="stylesheet" /> 45 <link href="../css/responsive.css" rel="stylesheet" /> 46 </head> 47 48 <body class="donate-page"> 49 <div id="page" class="page"> 50 <!-- HEADER --> 51 <header id="header" class="header"> 52 <nav 53 class="navbar fixed-top navbar-expand-md navbar-light bg-tra hover-menu donate-nav" 54 > 55 <div class="container"> 56 <a href="../" class="navbar-brand logo-black"> 57 <img 58 src="../images/logo.png" 59 width="120" 60 height="30" 61 alt="Polar logo" 62 /> 63 </a> 64 65 <button 66 class="navbar-toggler" 67 type="button" 68 data-toggle="collapse" 69 data-target="#navbarSupportedContent" 70 aria-controls="navbarSupportedContent" 71 aria-expanded="false" 72 aria-label="Toggle navigation" 73 > 74 <span class="navbar-bar-icon"><i class="fas fa-bars"></i></span> 75 </button> 76 77 <div id="navbarSupportedContent" class="collapse navbar-collapse"> 78 <ul class="navbar-nav ml-auto"> 79 <li class="nav-item nl-simple"> 80 <a class="nav-link" href="../">Home</a> 81 </li> 82 <li class="nav-item nl-simple"> 83 <a class="nav-link" href="../#services-1">Benefits</a> 84 </li> 85 <li class="nav-item nl-simple"> 86 <a class="nav-link" href="../#content-1">Features</a> 87 </li> 88 <li class="nav-item nl-simple"> 89 <a class="nav-link" href="../#reviews-2">Reviews</a> 90 </li> 91 <li class="nav-item nl-simple"> 92 <a class="nav-link" href="../#faqs-2">FAQs</a> 93 </li> 94 </ul> 95 96 <span class="navbar-text white-color"> 97 <a 98 href="../#hero-2" 99 class="btn btn-primary-color tra-black-hover" 100 >Download</a 101 > 102 </span> 103 </div> 104 </div> 105 </nav> 106 </header> 107 <!-- END HEADER --> 108 109 <!-- HERO --> 110 <section id="donate-hero" class="bg-lightgrey"> 111 <div class="container"> 112 <div class="row align-items-center"> 113 <div class="col-lg-6"> 114 <div class="hero-txt"> 115 <h1 class="h2-sm steelblue-color">Support the Polar project</h1> 116 <p class="p-md"> 117 Polar stays fast and free thanks to the contributions from the
118 community. Thank you for helping builders ship Lightning apps 119 faster. If youâd like to join them, add your handle and weâll 120 direct you to a quick, built-in payment page for a Lightning 121 or on-chain donation. 122 </p> 123 <div class="hero-benefits-wrap"> 124 <ul class="hero-benefits"> 125 <li> 126 Every supporter appears in the Supporters list by tier 127 </li> 128 <li> 129 Accepts Bitcoin, Lightning, and USD via GitHub Sponsors 130 </li> 131 <li>GitHub, Twitter, or Nostr handles for recognition</li> 132 <li>Code contributors recognized for their work</li> 133 </ul> 134 </div> 135 <a 136 class="btn btn-primary-color tra-black-hover" 137 href="#donate-flow" 138 >Donate BTC</a 139 > 140 <a 141 class="btn btn-outline-secondary d-inline-block ml-2" 142 href="https://github.com/sponsors/jamaljsr" 143 target="_blank" 144 rel="noopener" 145 >Donate USD</a 146 > 147 <p class="mt-2 mb-0 grey-color"> 148 Prefer to contribute code? Visit the 149 <a 150 href="https://github.com/jamaljsr/polar" 151 target="_blank" 152 rel="noopener" 153 >Polar repo</a 154 >. 155 </p> 156 </div> 157 </div> 158 <div class="col-lg-5 offset-lg-1"> 159 <div class="donate-hero-card"> 160 <div class="donate-hero-label">Bitcoin + Lightning</div> 161 <h4 class="h4-sm mt-10"> 162 Fuel development, testing, and support 163 </h4> 164 <p class="mb-0"> 165 Donations fund: faster releases, updated node images, docs, 166 and community help for Lightning builders. 167 </p> 168 <div class="donate-hero-meta mt-20"> 169 <div> 170 <span class="meta-label">Recognition</span> 171 <p class="mb-0">On this page and homepage</p> 172 </div> 173 <div> 174 <span class="meta-label">Payments</span> 175 <p class="mb-0">Bitcoin, Lightning, and USD</p> 176 </div> 177 </div> 178 </div> 179 </div> 180 </div> 181 </div> 182 </section> 183 <!-- END HERO --> 184 185 <!-- DONATE FLOW --> 186 <section 187 id="donate-flow" 188 class="wide-80 donate-flow division bg-white b-top" 189 > 190 <div class="container"> 191 <div class="row"> 192 <div class="col-lg-6"> 193 <div class="section-title mb-30"> 194 <h3 class="h3-md steelblue-color">Pick a Tier</h3> 195 <p class="p-md"> 196 Custom amounts map to the closest tier automatically. 197 </p> 198 </div> 199 <div class="tier-grid"> 200 <div class="tier-note p-md" role="note"> 201 <p class="p-sm mb-0"> 202 Any donation above 10K sats gets an honorable mention 203 </p> 204 </div> 205 <div class="tier-card active" data-tier="sparks"> 206 <div class="tier-label">Sparks</div> 207 <p class="tier-amount">100K sats</p> 208 <p class="tier-copy mb-0">Ignite growth.</p> 209 </div> 210 <div class="tier-card" data-tier="bolts"> 211 <div class="tier-label">Bolts</div> 212 <p class="tier-amount">500K sats</p> 213 <p class="tier-copy mb-0">Supercharge development.</p> 214 </div> 215 <div class="tier-card" data-tier="ion_drivers"> 216 <div class="tier-label">Ion Drivers</div> 217 <p class="tier-amount">1M sats</p> 218 <p class="tier-copy mb-0">Drive scaling.</p> 219 </div> 220 <div class="tier-card" data-tier="critical_mass"> 221 <div class="tier-label">Critical Mass</div> 222 <p class="tier-amount">10M+ sats</p> 223 <p class="tier-copy mb-0">Unleash full impact.</p> 224 </div> 225 </div> 226 </div> 227 228 <div class="col-lg-5 offset-lg-1"> 229 <div class="contribute-card"> 230 <div class="contribute-card-header"> 231 <h4 class="h4-sm mb-2">One-time BTC Donation</h4> 232 <p class="mb-0"> 233 You'll be redirected to our payment page to obtain a 234 Lightning invoice or Bitcoin on-chain address. 235 </p> 236 </div> 237 238 <div class="form-group amount-group"> 239 <label for="sats-amount">Amount</label> 240 <div class="input-group input-group-lg"> 241 <input 242 id="sats-amount" 243 type="text" 244 inputmode="numeric" 245 class="form-control" 246 value="100,000" 247 /> 248 <div class="input-group-append">
249 <span class="input-group-text">sats</span> 250 </div> 251 </div> 252 </div> 253 254 <div class="form-group recognition-group"> 255 <div class="custom-control custom-switch recognition-toggle"> 256 <input 257 type="checkbox" 258 checked="checked" 259 class="custom-control-input" 260 id="recognition-enabled" 261 /> 262 <label 263 class="custom-control-label" 264 for="recognition-enabled" 265 >Add me to the <strong>Supporters</strong> list below 266 </label> 267 </div> 268 </div> 269 270 <div id="recognition-fields" class="recognition-fields" hidden> 271 <div class="form-group"> 272 <label>Platform</label> 273 <div class="btn-group platform-toggle" role="group"> 274 <button 275 type="button" 276 class="btn btn-outline-secondary active" 277 data-platform="github" 278 > 279 GitHub 280 </button> 281 <button 282 type="button" 283 class="btn btn-outline-secondary" 284 data-platform="twitter" 285 > 286 Twitter / X 287 </button> 288 <button 289 type="button" 290 class="btn btn-outline-secondary" 291 data-platform="nostr" 292 > 293 Nostr 294 </button> 295 </div> 296 </div> 297 298 <div class="form-group"> 299 <label for="username">Handle</label> 300 <div class="input-group"> 301 <div class="input-group-prepend"> 302 <span class="input-group-text" id="platform-prefix" 303 >@</span 304 > 305 </div> 306 <input 307 id="username" 308 type="text" 309 class="form-control" 310 placeholder="username" 311 aria-describedby="platform-prefix" 312 /> 313 </div> 314 <small class="form-text text-muted"> 315 Shown publicly in the Supporters list. 316 </small> 317 </div> 318 </div> 319 320 <div class="contribute-actions"> 321 <a 322 id="pay-button" 323 class="btn btn-primary-color tra-black-hover btn-wide" 324 href="#" 325 >Donate</a 326 > 327 </div> 328 <p id="donation-error" class="text-danger mt-2 mb-0" hidden></p> 329 330 <p class="form-note mb-0 grey-color"> 331 After confirming payment, we'll add you to the matching tier 332 on this page and highlight top supporters on the homepage. 333 </p> 334 335 <p class="form-note mt-10 grey-color"> 336 Prefer fiat? Use the 337 <a 338 href="https://github.com/sponsors/jamaljsr" 339 target="_blank" 340 rel="noopener" 341 >GitHub Sponsors</a 342 > 343 page. 344 </p> 345 </div> 346 </div> 347 </div> 348 </div> 349 </section> 350 <!-- END DONATE FLOW --> 351 352 <!-- SUPPORTERS LISTS --> 353 <section id="supporters-lists" class="wide-80 supporters-lists division"> 354 <div class="container"> 355 <div class="row"> 356 <div class="col-lg-10 offset-lg-1 section-title"> 357 <h3 class="h3-md steelblue-color"> 358 Supporters & Contributors 359 </h3> 360 <p class="p-md"> 361 Huge thanks to everyone helping Polar run. Supporters and 362 contributors are recognized below. 363 </p> 364 </div> 365 </div> 366 367 <div class="supporter-level"> 368 <div class="supporter-level-header"> 369 <h4 class="h4-sm mb-1">Supporters</h4> 370 <p id="supporters-subtitle" class="mb-0"> 371 New supporters will appear here. 372 </p> 373 </div> 374 <div class="supporter-level-body"> 375 <div id="supporters-empty" class="row"> 376 <div class="col-lg-8"> 377 <div class="supporter-empty"> 378 <div class="supporter-empty-icon" aria-hidden="true"> 379 <i class="fas fa-star"></i> 380 </div> 381 <div class="supporter-empty-body"> 382 <div class="supporter-name">No supporters yet</div> 383 <p class="supporter-note mb-2"> 384 Be the first supporter and help fund Polarâs roadmap. 385 </p> 386 <a 387 class="btn btn-primary-color tra-black-hover btn-sm" 388 href="#donate-flow" 389 >Donate now</a 390 > 391 </div> 392 </div> 393 </div> 394 </div> 395 396 <div class="supporter-tiers"> 397 <div 398 id="critical-mass-section" 399 class="supporter-tier tier-critical_mass" 400 hidden 401 > 402 <div class="supporter-tier-header"> 403 <div class="supporter-tier-pill">Critical Mass</div> 404 <div class="supporter-tier-accent" aria-hidden="true"></div> 405 </div> 406 <div id="critical-mass-grid" class="supporter-grid"></div> 407 </div> 408 409 <div 410 id="ion-drivers-section" 411 class="supporter-tier tier-ion_drivers" 412 hidden 413 > 414 <div class="supporter-tier-header"> 415 <div class="supporter-tier-pill">Ion Drivers</div> 416 <div class="supporter-tier-accent" aria-hidden="true"></div> 417 </div>
418 <div id="ion-drivers-grid" class="supporter-grid"></div> 419 </div> 420 421 <div 422 id="bolts-section" 423 class="supporter-tier tier-bolts" 424 hidden 425 > 426 <div class="supporter-tier-header"> 427 <div class="supporter-tier-pill">Bolts</div> 428 <div class="supporter-tier-accent" aria-hidden="true"></div> 429 </div> 430 <div id="bolts-grid" class="supporter-grid"></div> 431 </div> 432 433 <div 434 id="sparks-section" 435 class="supporter-tier tier-sparks" 436 hidden 437 > 438 <div class="supporter-tier-header"> 439 <div class="supporter-tier-pill">Sparks</div> 440 <div class="supporter-tier-accent" aria-hidden="true"></div> 441 </div> 442 <div id="sparks-grid" class="supporter-grid"></div> 443 </div> 444 445 <div 446 id="honorable-section" 447 class="supporter-tier tier-honorable" 448 hidden 449 > 450 <div class="supporter-tier-header"> 451 <div class="supporter-tier-pill">Honorable Mentions</div> 452 </div> 453 <div id="honorable-grid" class="supporter-grid"></div> 454 </div> 455 </div> 456 </div> 457 </div> 458 459 <div class="supporter-level"> 460 <div class="supporter-level-header"> 461 <h4 class="h4-sm mb-1">Contributors</h4> 462 <p class="mb-3"> 463 Recognizing contributors whose code keeps Polar fast, stable, 464 and useful for the Lightning community. 465 </p> 466 </div> 467 <div id="contributors-list" class="row contributor-grid"></div> 468 </div> 469 </div> 470 </section> 471 <!-- END SUPPORTERS LISTS --> 472 473 <!-- FOOTER-4 474 ============================================= --> 475 <footer id="footer-4" class="wide-40 footer division"> 476 <div class="container"> 477 <!-- FOOTER CONTENT --> 478 <div class="row"> 479 <!-- FOOTER INFO --> 480 <div class="col-md-4 col-lg-4 col-xl-4"> 481 <div class="footer-info mb-40"> 482 <!-- Footer Logo --> 483 <img 484 src="../images/logo.png" 485 width="120" 486 height="30" 487 alt="footer-logo" 488 /> 489 490 <!-- Email --> 491 <p class="foo-email"> 492 <i class="fas fa-envelope"></i> 493 <a href="mailto:[email protected]" 494 >[email protected]</a 495 > 496 </p> 497 498 <!-- Social Icons --> 499 <div class="footer-socials-links mt-20"> 500 <ul class="foo-socials text-center clearfix"> 501 <li> 502 <a 503 href="https://github.com/jamaljsr/polar" 504 class="ico-github" 505 ><i class="fab fa-github"></i 506 ></a> 507 </li> 508 <li> 509 <a href="https://twitter.com/jamaljsr" class="ico-twitter" 510 ><i class="fab fa-twitter"></i 511 ></a> 512 </li> 513 </ul> 514 </div> 515 </div> 516 </div> 517 <!-- END FOOTER INFO --> 518 519 <!-- FOOTER PRODUCTS LINKS --> 520 <div class="col-md-1 col-lg-2 col-xl-2 offset-lg-1 offset-xl-2"> 521 <div class="footer-links mb-40"></div> 522 </div> 523 524 <!-- FOOTER COMPANY LINKS --> 525 <div class="col-md-3 col-lg-2 col-xl-2"> 526 <div class="footer-links mb-40"> 527 <!-- Title --> 528 <h5 class="h5-sm">Navigation</h5> 529 530 <!-- Footer Links --> 531 <ul class="clearfix"> 532 <li><a href="../#hero-2">Home</a></li> 533 <li><a href="../#services-1">Benefits</a></li> 534 <li><a href="../#content-1">Features</a></li> 535 <li><a href="../#reviews-2">Reviews</a></li> 536 <li><a href="/donate/">Donate</a></li> 537 <li><a href="../#faqs-2">FAQs</a></li> 538 </ul> 539 </div> 540 </div> 541 542 <!-- FOOTER --> 543 <div class="col-md-4 col-lg-3 col-xl-2">
544 <div class="footer-links mb-20"> 545 <!-- Title --> 546 <h5 class="h5-sm">Get The App</h5> 547 548 <a class="btn btn-black tra-black-hover dl-apple" href="#"> 549 <i class="fab fa-apple"></i> 550 Mac 551 </a> 552 553 <a class="btn btn-black tra-black-hover dl-linux" href="#"> 554 <i class="fab fa-linux"></i> 555 Linux 556 </a> 557 558 <a class="btn btn-black tra-black-hover dl-windows" href="#"> 559 <i class="fab fa-windows"></i> 560 Windows 561 </a> 562 </div> 563 </div> 564 <!-- END FOOTER NEWSLETTER FORM --> 565 </div> 566 <!-- END FOOTER CONTENT --> 567 568 <!-- FOOTER COPYRIGHT --> 569 <div class="bottom-footer"> 570 <div class="row"> 571 <div class="col-md-12"> 572 <p class="footer-copyright"> 573 © <span id="current-year">2024</span> <span>Polar</span>. 574 All Rights Reserved 575 </p> 576 </div> 577 </div> 578 </div> 579 </div> 580 <!-- End container --> 581 </footer> 582 <!-- END FOOTER-4 --> 583 </div> 584 <!-- END PAGE --> 585 586
586<script src="../js/jquery-3.3.1.min.js"></script>
586 587
587<script src="../js/bootstrap.min.js"></script>
587 588
588<script src="../js/jquery.easing.js"></script>
588 589
589<script src="../js/wow.js"></script>
589 590
590<script src="../js/downloads.js?v4.0.0"></script>
590 591
591<script src="../js/custom.js?v4.0.0"></script>
591 592
592<script> 593 new WOW().init(); 594 </script>
594 595
595<script> 596 (function () { 597 var el = document.getElementById('current-year'); 598 if (el) el.textContent = String(new Date().getFullYear()); 599 })(); 600 </script>
600 601
601<script> 602 (() => { 603 const tierMinimums = { 604 honorable: 10000, 605 sparks: 100000, 606 bolts: 500000, 607 ion_drivers: 1000000, 608 critical_mass: 10000000, 609 }; 610 611 const tierMinimumEntries = Object.entries(tierMinimums).sort( 612 (a, b) => a[1] - b[1] 613 ); 614 615 const platformPlaceholders = { 616 github: 'username', 617 twitter: 'handle', 618 nostr: 'npub1...', 619 }; 620 621 const tierCards = document.querySelectorAll('.tier-card'); 622 const amountInput = document.getElementById('sats-amount'); 623 const platformButtons = document.querySelectorAll('[data-platform]'); 624 const platformPrefix = document.getElementById('platform-prefix'); 625 const usernameInput = document.getElementById('username'); 626 const payButton = document.getElementById('pay-button'); 627 const recognitionToggle = document.getElementById( 628 'recognition-enabled' 629 ); 630 const recognitionFields = document.getElementById('recognition-fields'); 631 const donationError = document.getElementById('donation-error'); 632 const contributorsList = document.getElementById('contributors-list'); 633 634 let selectedTier = 'sparks'; 635 let selectedPlatform = 'github'; 636 const apiParam = new URLSearchParams(window.location.search).get('api'); 637 const apiPort = '8787'; 638 const host = window.location.hostname; 639 const isProdSite = 640 host === 'lightningpolar.com' || host.endsWith('.lightningpolar.com'); 641 642 const normalizeApiOverride = (value) => { 643 if (!value) return null; 644 try { 645 const u = new URL(value); 646 // In production, never allow arbitrary API override (prevents phishing/misdirection). 647 if (isProdSite) { 648 return u.origin === 'https://api.lightningpolar.com' 649 ? u.origin 650 : null; 651 } 652 // In non-prod, allow https anywhere, and allow http only for localhost. 653 if (u.protocol === 'https:') return u.origin; 654 const isLocalHttp = 655 u.protocol === 'http:' && 656 (u.hostname === 'localhost' || u.hostname === '127.0.0.1'); 657 return isLocalHttp ? u.origin : null; 658 } catch { 659 return null; 660 } 661 }; 662 663 const apiBase = 664 normalizeApiOverride(apiParam) || 665 (isProdSite 666 ? 'https://api.lightningpolar.com' 667 : `${window.location.protocol}//${host}:${apiPort}`); 668 669 const tierDisplayName = (tier) => { 670 const value = String(tier || '').trim(); 671 if (!value) return ''; 672 if (value === 'honorable') return 'Honorable Mention'; 673 if (value === 'critical_mass') return 'Critical Mass'; 674 if (value === 'ion_drivers') return 'Ion Drivers'; 675 if (value === 'bolts') return 'Bolts'; 676 if (value === 'sparks') return 'Sparks'; 677 return value 678 .split(/[_-]/g) 679 .filter(Boolean) 680 .map((p) => p.charAt(0).toUpperCase() + p.slice(1)) 681 .join(' '); 682 }; 683 684 const contributors = [ 685 { 686 name: 'kelvinator07', 687 url: 'https://github.com/kelvinator07', 688 avatar: 'https://unavatar.io/github/kelvinator07', 689 year: '2024', 690 }, 691 { 692 name: 'Abdulkbk', 693 url: 'https://github.com/Abdulkbk', 694 avatar: 'https://unavatar.io/github/Abdulkbk', 695 year: '2025', 696 }, 697 { 698 name: 'bolatovumar', 699 url: 'https://github.com/bolatovumar', 700 avatar: 'https://unavatar.io/github/bolatovumar', 701 year: '2021', 702 }, 703 { 704 name: 'Jem256', 705 url: 'https://github.com/Jem256', 706 avatar: 'https://unavatar.io/github/Jem256', 707 year: '2025', 708 }, 709 { 710 name: 'amovfx', 711 url: 'https://github.com/amovfx', 712 avatar: 'https://unavatar.io/github/amovfx', 713 year: '2023', 714 }, 715 { 716 name: 'torkelrogstad', 717 url: 'https://github.com/torkelrogstad', 718 avatar: 'https://unavatar.io/github/torkelrogstad', 719 year: '2020', 720 }, 721 { 722 name: 'louneskmt', 723 url: 'https://github.com/louneskmt', 724 avatar: 'https://unavatar.io/github/louneskmt', 725 year: '2022', 726 }, 727 { 728 name: 'ktecho', 729 url: 'https://github.com/ktecho', 730 avatar: 'https://unavatar.io/github/ktecho', 731 year: '2022', 732 }, 733 { 734 name: 'uwla', 735 url: 'https://github.com/uwla', 736 avatar: 'https://unavatar.io/github/uwla', 737 year: '2024', 738 }, 739 { 740 name: 'murtyjones', 741 url: 'https://github.com/murtyjones', 742 avatar: 'https://unavatar.io/github/murtyjones', 743 year: '2021', 744 }, 745 { 746 name: 'hsjoberg', 747 url: 'https://github.com/hsjoberg', 748 avatar: 'https://unavatar.io/github/hsjoberg', 749 year: '2022', 750 }, 751 { 752 name: 'Hakkadaikon', 753 url: 'https://github.com/Hakkadaikon', 754 avatar: 'https://unavatar.io/github/Hakkadaikon', 755 year: '2024', 756 }, 757 { 758 name: 'IgnacioPorte', 759 url: 'https://github.com/IgnacioPorte', 760 avatar: 'https://unavatar.io/github/IgnacioPorte', 761 year: '2025', 762 }, 763 { 764 name: 'porcupinenick', 765 url: 'https://github.com/porcupinenick', 766 avatar: 'https://unavatar.io/github/porcupinenick', 767 year: '2020', 768 }, 769 { 770 name: 'niteshbalusu11', 771 url: 'https://github.com/niteshbalusu11', 772 avatar: 'https://unavatar.io/github/niteshbalusu11', 773 year: '2022', 774 }, 775 { 776 name: 'hieblmi', 777 url: 'https://github.com/hieblmi', 778 avatar: 'https://unavatar.io/github/hieblmi', 779 year: '2023', 780 }, 781 { 782 name: 'AdamuAbba', 783 url: 'https://github.com/AdamuAbba', 784 avatar: 'https://unavatar.io/github/AdamuAbba', 785 year: '2025', 786 }, 787 { 788 name: 'cstenglein', 789 url: 'https://github.com/cstenglein', 790 avatar: 'https://unavatar.io/github/cstenglein', 791 year: '2021', 792 }, 793 { 794 name: 'kaloudis', 795 url: 'https://github.com/kaloudis', 796 avatar: 'https://unavatar.io/github/kaloudis', 797 year: '2020', 798 }, 799 { 800 name: 'federicobond', 801 url: 'https://github.com/federicobond', 802 avatar: 'https://unavatar.io/github/federicobond', 803 year: '2019', 804 }, 805 { 806 name: 'Fideltodayy', 807 url: 'https://github.com/Fideltodayy', 808 avatar: 'https://unavatar.io/github/Fideltodayy', 809 year: '2025', 810 }, 811 { 812 name: 'qustavo', 813 url: 'https://github.com/qustavo', 814 avatar: 'https://unavatar.io/github/qustavo', 815 year: '2021', 816 }, 817 { 818 name: 'NonsoAmadi10', 819 url: 'https://github.com/NonsoAmadi10', 820 avatar: 'https://unavatar.io/github/NonsoAmadi10', 821 year: '2023', 822 }, 823 { 824 name: 'jcbrand', 825 url: 'https://github.com/jcbrand', 826 avatar: 'https://unavatar.io/github/jcbrand', 827 year: '2025', 828 }, 829 { 830 name: 'Jasonvdb', 831 url: 'https://github.com/Jasonvdb', 832 avatar: 'https://unavatar.io/github/Jasonvdb', 833 year: '2020', 834 }, 835 { 836 name: 'halseth', 837 url: 'https://github.com/halseth', 838 avatar: 'https://unavatar.io/github/halseth', 839 year: '2020', 840 }, 841 { 842 name: 'Jhoyola', 843 url: 'https://github.com/Jhoyola', 844 avatar: 'https://unavatar.io/github/Jhoyola', 845 year: '2023', 846 }, 847 { 848 name: 'lukechilds', 849 url: 'https://github.com/lukechilds', 850 avatar: 'https://unavatar.io/github/lukechilds', 851 year: '2020', 852 }, 853 { 854 name: 'Mshehu5', 855 url: 'https://github.com/Mshehu5', 856 avatar: 'https://unavatar.io/github/Mshehu5', 857 year: '2025', 858 }, 859 { 860 name: 'NehaK745', 861 url: 'https://github.com/NehaK745', 862 avatar: 'https://unavatar.io/github/NehaK745', 863 year: '2021', 864 }, 865 { 866 name: 'ottosuess', 867 url: 'https://github.com/ottosuess', 868 avatar: 'https://unavatar.io/github/ottosuess', 869 year: '2019', 870 }, 871 { 872 name: 'SeverinAlexB', 873 url: 'https://github.com/SeverinAlexB', 874 avatar: 'https://unavatar.io/github/SeverinAlexB', 875 year: '2023', 876 }, 877 { 878 name: 'valentinewallace', 879 url: 'https://github.com/valentinewallace', 880 avatar: 'https://unavatar.io/github/valentinewallace', 881 year: '2021', 882 }, 883 { 884 name: 'wbobeirne', 885 url: 'https://github.com/wbobeirne', 886 avatar: 'https://unavatar.io/github/wbobeirne', 887 year: '2019', 888 }, 889 { 890 name: 'ramigs', 891 url: 'https://github.com/ramigs', 892 avatar: 'https://unavatar.io/github/ramigs', 893 year: '2023', 894 }, 895 { 896 name: 'git-sgmoore', 897 url: 'https://github.com/git-sgmoore', 898 avatar: 'https://unavatar.io/github/git-sgmoore', 899 year: '2021', 900 }, 901 { 902 name: 'soonsouth', 903 url: 'https://github.com/soonsouth', 904 avatar: 'https://unavatar.io/github/soonsouth', 905 year: '2024', 906 }, 907 { 908 name: 'zackypick', 909 url: 'https://github.com/zackypick', 910 avatar: 'https://unavatar.io/github/zackypick', 911 year: '2022', 912 }, 913 {
914 name: 'See all contributors', 915 role: 'Add your name to the list', 916 url: 'https://github.com/jamaljsr/polar/graphs/contributors', 917 cta: true, 918 }, 919 ]; 920 921 const setPayButtonEnabled = (enabled) => { 922 if (!payButton) return; 923 const isEnabled = Boolean(enabled); 924 payButton.classList.toggle('disabled', !isEnabled); 925 if (isEnabled) { 926 payButton.removeAttribute('aria-disabled'); 927 } else { 928 payButton.setAttribute('aria-disabled', 'true'); 929 } 930 }; 931 932 const formatAmount = (value) => 933 Number(value).toLocaleString('en-US', { maximumFractionDigits: 0 }); 934 935 const parseAmountSat = (rawValue) => { 936 const raw = String(rawValue ?? '').trim(); 937 const cleaned = raw.replace(/[,\s]/g, ''); 938 939 if (!cleaned) { 940 return { ok: false, error: 'Enter an amount in sats.' }; 941 } 942 if (!/^\d+$/.test(cleaned)) { 943 return { ok: false, error: 'Amount must be a whole number.' }; 944 } 945 946 const amountSat = Number(cleaned); 947 if (!Number.isSafeInteger(amountSat)) { 948 return { ok: false, error: 'Amount is too large.' }; 949 } 950 if (amountSat <= 0) { 951 return { ok: false, error: 'Amount must be at least 1,000 sats.' }; 952 } 953 if (amountSat < 1000) { 954 return { 955 ok: false, 956 error: 'Amount must be at least 1,000 sats.', 957 }; 958 } 959 960 return { ok: true, amountSat }; 961 }; 962 963 const clearTierSelection = () => { 964 selectedTier = null; 965 tierCards.forEach((card) => card.classList.remove('active')); 966 }; 967 968 const matchTierForAmount = (amountSat) => { 969 let matchedTier = null; 970 tierMinimumEntries.forEach(([tier, min]) => { 971 if (amountSat >= min) matchedTier = tier; 972 }); 973 return matchedTier; 974 }; 975 976 const updateTierFromAmountOnBlur = () => { 977 const parsed = parseAmountSat(amountInput?.value); 978 if (!parsed.ok) { 979 showError(parsed.error); 980 setPayButtonEnabled(false); 981 return null; 982 } 983 984 showError(''); 985 setPayButtonEnabled(true); 986 987 const matchedTier = matchTierForAmount(parsed.amountSat); 988 if (!matchedTier || matchedTier === 'honorable') { 989 clearTierSelection(); 990 } else { 991 selectedTier = matchedTier; 992 tierCards.forEach((card) => 993 card.classList.toggle('active', card.dataset.tier === matchedTier) 994 ); 995 } 996 997 amountInput.value = formatAmount(parsed.amountSat); 998 return parsed.amountSat; 999 }; 1000 1001 const setTier = (tier) => { 1002 selectedTier = tier; 1003 const min = tierMinimums[tier]; 1004 amountInput.value = formatAmount(min); 1005 1006 tierCards.forEach((card) => 1007 card.classList.toggle('active', card.dataset.tier === tier) 1008 ); 1009 1010 showError(''); 1011 setPayButtonEnabled(true); 1012 }; 1013 1014 const setPlatform = (platform) => { 1015 selectedPlatform = platform; 1016 platformButtons.forEach((btn) => 1017 btn.classList.toggle('active', btn.dataset.platform === platform) 1018 ); 1019 platformPrefix.textContent = platform === 'nostr' ? 'ð' : '@'; 1020 usernameInput.placeholder = platformPlaceholders[platform]; 1021 }; 1022 1023 const showError = (message) => { 1024 if (!donationError) return; 1025 donationError.hidden = !message; 1026 donationError.textContent = message || ''; 1027 }; 1028 1029 const createDonation = async (includeIdentity = true) => { 1030 const parsed = parseAmountSat(amountInput?.value); 1031 if (!parsed.ok) { 1032 showError(parsed.error); 1033 setPayButtonEnabled(false); 1034 throw new Error(parsed.error); 1035 } 1036 const amountSat = parsed.amountSat;
1037 const matchedTier = matchTierForAmount(amountSat); 1038 if (!matchedTier || matchedTier === 'honorable') { 1039 clearTierSelection(); 1040 } else { 1041 selectedTier = matchedTier; 1042 tierCards.forEach((card) => 1043 card.classList.toggle('active', card.dataset.tier === matchedTier) 1044 ); 1045 } 1046 amountInput.value = formatAmount(amountSat); 1047 showError(''); 1048 setPayButtonEnabled(true); 1049 1050 const username = usernameInput.value.trim(); 1051 1052 const payload = { amountSat }; 1053 if (includeIdentity && username) { 1054 payload.platform = selectedPlatform; 1055 payload.username = username; 1056 } 1057 1058 const res = await fetch(`${apiBase}/donations`, { 1059 method: 'POST', 1060 headers: { 'content-type': 'application/json' }, 1061 body: JSON.stringify(payload), 1062 }); 1063 1064 const data = await res.json().catch(() => null); 1065 if (!res.ok) { 1066 throw new Error(data?.error?.message || 'Unable to start payment.'); 1067 } 1068 1069 if (!data?.donationId) { 1070 throw new Error('Unexpected response from server.'); 1071 } 1072 1073 return data.donationId; 1074 }; 1075 1076 const fetchSupporters = async () => { 1077 const res = await fetch(`${apiBase}/supporters`, { method: 'GET' }); 1078 if (!res.ok) return []; 1079 const data = await res.json().catch(() => null); 1080 return Array.isArray(data?.supporters) ? data.supporters : []; 1081 }; 1082 1083 const safeUrl = (value) => { 1084 try { 1085 const u = new URL(value); 1086 return u.protocol === 'https:' ? u.toString() : null; 1087 } catch { 1088 return null; 1089 } 1090 }; 1091 1092 const labelFromProfile = (profileUrl) => { 1093 try { 1094 const u = new URL(profileUrl); 1095 const last = 1096 u.pathname.split('/').filter(Boolean).pop() || u.hostname; 1097 return last.length > 22 ? `${last.slice(0, 22)}â¦` : last; 1098 } catch { 1099 return 'Supporter'; 1100 } 1101 }; 1102 1103 const displayNameForSupporter = (supporter) => { 1104 const username = 1105 supporter && typeof supporter.username === 'string' 1106 ? supporter.username.trim() 1107 : ''; 1108 if (username) { 1109 return username.length > 22 1110 ? `${username.slice(0, 22)}â¦` 1111 : username; 1112 } 1113 1114 const profileUrl = supporter?.profileUrl; 1115 return typeof profileUrl === 'string' 1116 ? labelFromProfile(profileUrl) 1117 : 'Supporter'; 1118 }; 1119 1120 const renderSupporterTier = (gridEl, supporters, tier) => { 1121 if (!gridEl) return; 1122 gridEl.innerHTML = ''; 1123 1124 const tierSupporters = supporters.filter((s) => s?.tier === tier); 1125 if (!tierSupporters.length) return; 1126 1127 tierSupporters.forEach((s) => { 1128 const profileUrl = safeUrl(s.profileUrl); 1129 const imageUrl = safeUrl(s.imageUrl); 1130 if (!profileUrl || !imageUrl) return; 1131 1132 const tile = document.createElement('a'); 1133 tile.className = `supporter-tile tier-${tier}`; 1134 tile.href = profileUrl; 1135 tile.target = '_blank'; 1136 tile.rel = 'noopener noreferrer'; 1137 1138 const img = document.createElement('img'); 1139 img.src = imageUrl; 1140 img.alt = `${displayNameForSupporter(s)} avatar`; 1141 img.loading = 'lazy'; 1142 img.referrerPolicy = 'no-referrer'; 1143 1144 const avatar = document.createElement('div'); 1145 avatar.className = 'supporter-avatar'; 1146 1147 const avatarInner = document.createElement('div'); 1148 avatarInner.className = 'supporter-avatar-inner'; 1149 avatarInner.appendChild(img); 1150 avatar.appendChild(avatarInner); 1151 1152 if (tier !== 'honorable') { 1153 const namePill = document.createElement('div'); 1154 namePill.className = 'supporter-name-pill'; 1155 namePill.textContent = displayNameForSupporter(s); 1156 avatar.appendChild(namePill); 1157 } 1158 1159 tile.appendChild(avatar); 1160 1161 gridEl.appendChild(tile); 1162 }); 1163 }; 1164 1165 const renderContributors = () => { 1166 if (!contributorsList) return; 1167 1168 contributorsList.innerHTML = ''; 1169
1170 contributors.forEach(({ name, role, url, avatar, year, cta }) => { 1171 const col = document.createElement('div'); 1172 col.className = 'col-6 col-lg-4'; 1173 1174 const tile = document.createElement(url ? 'a' : 'div'); 1175 tile.className = `contributor-tile${cta ? ' cta' : ''}`; 1176 1177 if (url) { 1178 tile.href = url; 1179 tile.target = '_blank'; 1180 tile.rel = 'noopener'; 1181 } 1182 1183 const avatarEl = document.createElement('div'); 1184 avatarEl.className = 'contributor-avatar'; 1185 if (!cta && avatar) { 1186 const img = document.createElement('img'); 1187 img.src = avatar; 1188 img.alt = `${name} avatar`; 1189 img.loading = 'lazy'; 1190 img.referrerPolicy = 'no-referrer'; 1191 img.onerror = () => { 1192 avatarEl.textContent = (name || '?').charAt(0).toUpperCase(); 1193 img.remove(); 1194 }; 1195 avatarEl.appendChild(img); 1196 } else { 1197 avatarEl.textContent = (name || '?').charAt(0).toUpperCase(); 1198 } 1199 1200 const meta = document.createElement('div'); 1201 1202 const title = document.createElement('div'); 1203 title.className = 'contributor-name'; 1204 title.textContent = name; 1205 1206 const subtitle = document.createElement('p'); 1207 subtitle.className = 'contributor-role mb-0'; 1208 subtitle.textContent = year 1209 ? `${year} ${role || 'Contributor'}` 1210 : role || 'Contributor'; 1211 1212 meta.appendChild(title); 1213 meta.appendChild(subtitle); 1214 1215 tile.appendChild(avatarEl); 1216 tile.appendChild(meta); 1217 col.appendChild(tile); 1218 contributorsList.appendChild(col); 1219 }); 1220 }; 1221 1222 tierCards.forEach((card) => { 1223 card.addEventListener('click', () => setTier(card.dataset.tier)); 1224 }); 1225 1226 platformButtons.forEach((btn) => { 1227 btn.addEventListener('click', () => 1228 setPlatform(btn.dataset.platform) 1229 ); 1230 }); 1231 1232 amountInput.addEventListener('focus', () => { 1233 showError(''); 1234 setPayButtonEnabled(true); 1235 amountInput.value = String(amountInput.value).replace(/,/g, ''); 1236 }); 1237 1238 amountInput.addEventListener('blur', () => { 1239 updateTierFromAmountOnBlur(); 1240 }); 1241 1242 amountInput.addEventListener('input', () => { 1243 showError(''); 1244 setPayButtonEnabled(true); 1245 }); 1246 1247 const updateRecognitionUI = () => { 1248 const enabled = Boolean(recognitionToggle?.checked); 1249 if (recognitionFields) recognitionFields.hidden = !enabled; 1250 if (!enabled && usernameInput) usernameInput.value = ''; 1251 }; 1252 1253 recognitionToggle?.addEventListener('change', updateRecognitionUI); 1254 1255 payButton.addEventListener('click', (event) => { 1256 event.preventDefault(); 1257 showError(''); 1258 1259 // Block donation if the amount is invalid (even if the user never blurs). 1260 const parsed = parseAmountSat(amountInput?.value); 1261 if (!parsed.ok) { 1262 showError(parsed.error); 1263 return; 1264 } 1265 1266 setPayButtonEnabled(false); 1267 1268 createDonation(Boolean(recognitionToggle?.checked)) 1269 .then((donationId) => { 1270 window.location.href = `/donate/pay/?donation=${encodeURIComponent( 1271 donationId 1272 )}`; 1273 }) 1274 .catch((err) => { 1275 showError(err instanceof Error ? err.message : String(err)); 1276 setPayButtonEnabled(true); 1277 }); 1278 }); 1279 1280 // Initialize defaults 1281 setTier('sparks'); 1282 setPlatform(selectedPlatform); 1283 updateRecognitionUI(); 1284 renderContributors(); 1285 1286 // Render Supporters list from API (paid + published only) 1287 const criticalMassGrid = document.getElementById('critical-mass-grid'); 1288 const ionDriversGrid = document.getElementById('ion-drivers-gri
1288d'); 1289 const boltsGrid = document.getElementById('bolts-grid'); 1290 const sparksGrid = document.getElementById('sparks-grid'); 1291 const honorableGrid = document.getElementById('honorable-grid'); 1292 1293 const supportersSubtitle = document.getElementById( 1294 'supporters-subtitle' 1295 ); 1296 1297 const supportersEmpty = document.getElementById('supporters-empty'); 1298 const criticalMassSection = document.getElementById( 1299 'critical-mass-section' 1300 ); 1301 const ionDriversSection = document.getElementById( 1302 'ion-drivers-section' 1303 ); 1304 const boltsSection = document.getElementById('bolts-section'); 1305 const sparksSection = document.getElementById('sparks-section'); 1306 const honorableSection = document.getElementById('honorable-section'); 1307 1308 const hideAllTiers = () => { 1309 if (criticalMassSection) criticalMassSection.hidden = true; 1310 if (ionDriversSection) ionDriversSection.hidden = true; 1311 if (boltsSection) boltsSection.hidden = true; 1312 if (sparksSection) sparksSection.hidden = true; 1313 if (honorableSection) honorableSection.hidden = true; 1314 1315 if (criticalMassGrid) criticalMassGrid.innerHTML = ''; 1316 if (ionDriversGrid) ionDriversGrid.innerHTML = ''; 1317 if (boltsGrid) boltsGrid.innerHTML = ''; 1318 if (sparksGrid) sparksGrid.innerHTML = ''; 1319 if (honorableGrid) honorableGrid.innerHTML = ''; 1320 }; 1321 1322 const showSupporters = (supporters) => { 1323 const list = Array.isArray(supporters) ? supporters : []; 1324 if (!list.length) { 1325 if (supportersSubtitle) { 1326 supportersSubtitle.textContent = 1327 'New supporters will appear here.'; 1328 } 1329 if (supportersEmpty) supportersEmpty.hidden = false; 1330 hideAllTiers(); 1331 return; 1332 } 1333 1334 if (supportersSubtitle) { 1335 supportersSubtitle.textContent = 1336 'From all of us Polar users, thank you for supporting its ongoing development. Your generosity keeps the project moving forward and helps the whole Lightning community build.'; 1337 } 1338 1339 if (supportersEmpty) supportersEmpty.hidden = true; 1340 1341 const criticalMass = list.filter((s) => s?.tier === 'critical_mass'); 1342 const ionDrivers = list.filter((s) => s?.tier === 'ion_drivers'); 1343 const bolts = list.filter((s) => s?.tier === 'bolts'); 1344 const sparks = list.filter((s) => s?.tier === 'sparks'); 1345 const honorable = list.filter((s) => s?.tier === 'honorable'); 1346 1347 if (criticalMassSection) 1348 criticalMassSection.hidden = criticalMass.length === 0; 1349 if (ionDriversSection) 1350 ionDriversSection.hidden = ionDrivers.length === 0; 1351 if (boltsSection) boltsSection.hidden = bolts.length === 0; 1352 if (sparksSection) sparksSection.hidden = sparks.length === 0; 1353 if (honorableSection) 1354 honorableSection.hidden = honorable.length === 0; 1355 1356 renderSupporterTier(criticalMassGrid, list, 'critical_mass'); 1357 renderSupporterTier(ionDriversGrid, list, 'ion_drivers'); 1358 renderSupporterTier(boltsGrid, list, 'bolts'); 1359 renderSupporterTier(sparksGrid, list, 'sparks'); 1360 renderSupporterTier(honorableGrid, list, 'honorable'); 1361 }; 1362 1363 // Default UI while loading (and if API is unavailable) 1364 if (supportersEmpty) supportersEmpty.hidden = false; 1365 hideAllTiers(); 1366 1367 fetchSupporters() 1368 .then((supporters) => { 1369 showSupporters(supporters); 1370 }) 1371 .catch(() => { 1372 // If the API is unavailable, still show the empty state. 1373 if (supportersEmpty) supportersEmpty.hidden = false; 1374 hideAllTiers(); 1375 }); 1376 })(); 1377 </script>
1377 1378 </body> 1379</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.