PageSourceSearch

https://lightningpolar.com/donate/

html lightningpolar.com collected 2026-10-03 22:11:06 UTC 49,771 bytes, 1,379 lines download raw bytes

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 &amp; 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                  &copy; <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.