PageSourceSearch

https://www.ecoflix.com/donate/

html ecoflix.com collected 2026-09-24 19:52:47 UTC 127,873 bytes, 2,803 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en">
3<head>
4<meta charset="UTF-8" />
5<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6<title>Donate to Wildlife Conservation · Ecoflix Foundation</title>
7<meta name="description" content="100% of every donation reaches the partner NGO doing the work. Pick an amount and see exactly what it funds. Ecoflix Foundation takes nothing from your gift." />
8<meta name="theme-color" content="#020718" />
9<link rel="canonical" href="https://www.ecoflix.com/donate/" />
10<link rel="preconnect" href="https://fonts.googleapis.com" />
11<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
12<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;700&family=Red+Hat+Display:ital,wght@0,500;0,800;0,900;1,500&display=swap" rel="stylesheet" />
13<link rel="icon" type="image/png" href="/assets/favicon.png" />
14<link rel="stylesheet" href="/assets/site.css" />
15<link rel="stylesheet" href="/assets/i18n.css" />
16<style>
17  /* =========================================================
18     /donate/  —  conversion page, no external JS deps.
19     Reuses tokens (--ink, --bone, --leaf, --mist, --display,
20     --body, --pad-x) and shared classes (.btn, .btn-primary,
21     .btn-ghost, .eyebrow, .wrap, .sdb, .header, .coord-stamp,
22     .bg-cartograph, .footer) from /assets/site.css.
23     ========================================================= */
24
25  /* Hero */
26  .pg-hero{
27    position:relative;padding:160px var(--pad-x) 40px;
28    text-align:center;overflow:hidden;isolation:isolate;
29  }
30  .pg-hero::before{
31    content:"";position:absolute;inset:-20%;z-index:-1;
32    background:radial-gradient(45% 40% at 50% 50%, rgba(168,243,65,.22), transparent 70%);
33    filter:blur(50px);
34  }
35  .pg-hero h1{
36    font-family:var(--display);font-weight:800;
37    font-size:clamp(48px,7.4vw,116px);line-height:.96;letter-spacing:-.03em;
38    margin:14px auto 18px;max-width:16ch;
39  }
40  .pg-hero h1 em{
41    font-style:italic;font-weight:500;
42    background:linear-gradient(120deg, var(--leaf) 0%, #D3FA9E 45%, var(--leaf) 100%);
43    background-size:200% 100%;
44    -webkit-background-clip:text;background-clip:text;color:transparent;
45    animation:shine 7s linear infinite;
46  }
47  @keyframes shine{0%{background-position:0% 0}100%{background-position:200% 0}}
48  .pg-hero p.lede{
49    max-width:64ch;margin:0 auto;
50    font-size:clamp(17px,1.6vw,21px);color:var(--mist);line-height:1.55;
51  }
52
53  /* Donation picker */
54  .donate-section{
55    position:relative;padding:40px var(--pad-x) 100px;isolation:isolate;
56  }
57  .donate-section::before{
58    content:"";position:absolute;inset:0;z-index:-1;
59    background:radial-gradient(40% 35% at 75% 50%, rgba(168,243,65,.18), transparent 70%);
60    filter:blur(60px);pointer-events:none;
61  }
62  .donate-shell{
63    max-width:760px;margin:0 auto;
64    background:rgba(2,7,24,.6);
65    border:1px solid rgba(255,255,255,.06);
66    border-radius:24px;
67    padding:clamp(28px,4vw,48px);
68    box-shadow:0 20px 60px rgba(0,0,0,.45), inset 0 0 0 1px rgba(168,243,65,.04);
69    position:relative;
70    backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
71  }
72  .donate-shell .coord-stamp{margin-bottom:16px}
73
74  /* ============================================================
75     Contextual locale card — sits top-right of the donate shell
76     in the empty space the user circled. Shows visitor's flag,
77     city / country, lat/lon and the currency we've converted to.
78     Replaces the top-of-page locale bar on this page (donate has
79     data-i18n-no-bar set on <body>).
80     ============================================================ */
81  .donate-shell{padding-top:clamp(28px,4vw,48px)}
82  @media (min-width:760px){
83    .donate-shell{padding-top:clamp(32px,5vw,56px)}
84  }
85  .dp-locale{
86    position:absolute;top:18px;right:18px;
87    display:flex;flex-direction:column;gap:6px;
88    padding:12px 14px;border-radius:14px;
89    background:rgba(168,243,65,.06);
90    border:1px solid rgba(168,243,65,.28);
91    min-width:172px;max-width:230px;
92    font-family:var(--display);text-align:right;
93    transition:border-color .2s ease, transform .15s ease, background .2s ease;
94    cursor:default;z-index:2;
95  }
96  .dp-locale:hover{border-color:rgba(168,243,65,.55);background:rgba(168,243,65,.10)}
97  .dp-locale .dpl-eyebrow{
98    display:flex;align-items:center;justify-content:flex-end;
98gap:6px;
99    font-weight:700;font-size:9.5px;letter-spacing:.22em;text-transform:uppercase;
100    color:var(--leaf);
101  }
102  .dp-locale .dpl-eyebrow::before{
103    content:"";width:6px;height:6px;border-radius:50%;background:var(--leaf);
104    box-shadow:0 0 8px var(--leaf-glow);animation:dpl-blink 1.4s ease-in-out infinite;
105  }
106  @keyframes dpl-blink{0%,100%{opacity:1}50%{opacity:.4}}
107  .dp-locale .dpl-flag-line{
108    display:flex;align-items:center;justify-content:flex-end;gap:6px;
109    font-weight:800;font-size:13.5px;color:var(--bone);letter-spacing:-.005em;
110    line-height:1.2;
111  }
112  .dp-locale .dpl-flag-line .flag{font-size:16px;line-height:1}
113  .dp-locale .dpl-where{
114    font-family:var(--body);font-weight:500;font-size:11.5px;color:var(--mist);
115    line-height:1.3;letter-spacing:.005em;
116  }
117  .dp-locale .dpl-coord{
118    font-family:var(--display);font-weight:700;font-size:10.5px;
119    color:var(--mist-2);font-variant-numeric:tabular-nums;letter-spacing:.04em;
120    padding-top:6px;margin-top:2px;border-top:1px dashed rgba(168,243,65,.22);
121  }
122  .dp-locale .dpl-ccy{
123    display:inline-flex;align-items:center;justify-content:flex-end;gap:5px;
124    font-weight:800;font-size:12.5px;color:var(--leaf);
125    letter-spacing:.04em;
126  }
127  .dp-locale .dpl-ccy .dpl-ccy-note{
128    color:var(--mist-2);font-weight:600;font-size:9.5px;letter-spacing:.18em;
129    text-transform:uppercase;
130  }
131  .dp-locale .dpl-toggle{
132    display:inline-flex;align-items:center;justify-content:center;gap:4px;
133    margin-top:6px;padding:5px 10px;border-radius:999px;
134    background:rgba(2,7,24,.6);border:1px solid rgba(168,243,65,.30);
135    color:var(--leaf);font-weight:700;font-size:9.5px;letter-spacing:.18em;
136    text-transform:uppercase;text-decoration:none;cursor:pointer;
137    transition:background .2s, border-color .2s;
138  }
139  .dp-locale .dpl-toggle:hover{background:rgba(168,243,65,.14);border-color:rgba(168,243,65,.55)}
140  .dp-locale.is-default .dpl-toggle{display:none}
141  /* Tuck the card under the header on narrow screens so it doesn't
142     squash the picker. */
143  @media (max-width:640px){
144    .dp-locale{
145      position:static;display:grid;grid-template-columns:1fr auto;
146      grid-template-areas:
147        "a b"
148        "c c"
149        "d e";
150      gap:6px 14px;margin:0 0 18px;text-align:left;max-width:none;align-items:center;
151    }
152    .dp-locale .dpl-eyebrow{grid-area:a;justify-content:flex-start}
153    .dp-locale .dpl-flag-line{grid-area:b;justify-content:flex-end}
154    .dp-locale .dpl-where{grid-area:c;text-align:left}
155    .dp-locale .dpl-coord{grid-area:d;text-align:left;border-top:none;padding-top:0;margin-top:0}
156    .dp-locale .dpl-ccy{grid-area:e;justify-content:flex-end}
157    .dp-locale .dpl-toggle{display:none !important}
158  }
159  .donate-h{
160    font-family:var(--display);font-weight:800;
161    font-size:clamp(26px,3vw,38px);line-height:1.05;letter-spacing:-.02em;
162    margin-bottom:6px;
163  }
164  .donate-h em{font-style:italic;font-weight:500;color:var(--leaf)}
165  .donate-sub{color:var(--mist);font-size:15px;margin-bottom:24px}
166
167  .freq-toggle{
168    display:inline-flex;background:rgba(255,255,255,.04);
169    border:1px solid rgba(255,255,255,.08);
170    border-radius:999px;padding:4px;margin-bottom:22px;gap:2px;
171  }
172  .freq-toggle button{
173    padding:9px 18px;border-radius:999px;font-size:13px;font-weight:600;
174    color:var(--mist);letter-spacing:.02em;
175    transition:color .2s, background .2s;
176  }
177  .freq-toggle button.is-on{
178    background:var(--leaf);color:var(--ink);
179    box-shadow:inset 0 0 0 1px rgba(0,0,0,.05);
180  }
181  .freq-toggle button:hover:not(.is-on){color:var(--bone)}
182
183  .amount-grid{
184    display:grid;grid-template-columns:repeat(4, 1fr);gap:12px;margin-bottom:18px;
185  }
186  @media (max-width:560px){.amount-grid{grid-template-columns:repeat(2, 1fr)}}
187  .amount-tile{
188    position:relative;
189    padding:22px 14px;border-radius:14px;
190    background:rgba(255,255,255,.03);
191    border:1px solid rgba(255,255,255,.08);
192    font-family:var(--display);font-weight:800;font-size:26px;letter-spacing:-.01em;
193    color:var(--bone);
194    transition:all .2s ease;
195    cursor:pointer;
196  }
197  .amount-tile:hover{border-color:rgba(168,243,65,.4);transform:translateY(-2px)}
198  .amount-tile.is-on{
199    background:rgba(168,243,65,.10);
200    border-color:var(--leaf);color:var(--bone);
201    box-shadow:0 8px 24px rgba(168,243,65,.18);
202  }
203  .amount-tile .at-curr{font-size:14px;
203color:var(--mist);font-weight:700;margin-right:2px;vertical-align:top;line-height:1}
204  .amount-tile .at-suf{display:block;font-family:var(--body);font-weight:600;font-size:10.5px;letter-spacing:.18em;text-transform:uppercase;color:var(--mist-2);margin-top:6px}
205
206  .custom-row{
207    display:flex;align-items:center;gap:10px;
208    background:rgba(255,255,255,.03);
209    border:1px solid rgba(255,255,255,.08);
210    border-radius:12px;padding:6px 14px 6px 16px;
211    margin-bottom:24px;
212    transition:border-color .2s;
213  }
214  .custom-row:focus-within{border-color:var(--leaf)}
215  .custom-row label{
216    font-family:var(--body);font-size:11px;letter-spacing:.18em;
217    text-transform:uppercase;color:var(--mist-2);font-weight:600;
218  }
219  .custom-row .cs-curr{font-family:var(--display);font-weight:800;font-size:20px;color:var(--mist)}
220  .custom-row input{
221    flex:1;background:transparent;border:0;outline:0;
222    color:var(--bone);font-family:var(--display);font-weight:800;
223    font-size:22px;letter-spacing:-.01em;padding:10px 0;min-width:0;
224  }
225  .custom-row input::placeholder{color:var(--mist-2)}
226
227  .funds-card{
228    display:flex;gap:14px;align-items:flex-start;
229    padding:18px 18px;border-radius:14px;
230    background:rgba(168,243,65,.06);
231    border:1px solid rgba(168,243,65,.22);
232    margin-bottom:24px;
233    transition:background .25s;
234  }
235  .funds-icon{
236    flex:0 0 38px;width:38px;height:38px;border-radius:10px;
237    display:grid;place-items:center;
238    background:rgba(168,243,65,.16);color:var(--leaf);
239  }
240  .funds-icon svg{width:20px;height:20px;stroke-width:1.8}
241  .funds-text{flex:1;min-width:0}
242  .funds-eyebrow{
243    font-family:var(--body);font-size:10.5px;letter-spacing:.22em;
244    text-transform:uppercase;color:var(--leaf);font-weight:700;margin-bottom:4px;
245  }
246  .funds-line{
247    font-family:var(--display);font-weight:700;font-size:17px;line-height:1.3;color:var(--bone);
248  }
249
250  .donate-cta{
251    display:flex;width:100%;justify-content:center;align-items:center;gap:10px;
252    padding:18px 22px;border-radius:6px;
253    background:var(--leaf);color:var(--ink);
254    font-family:var(--display);font-weight:800;font-size:16px;letter-spacing:.005em;
255    box-shadow:0 12px 32px rgba(168,243,65,.28), inset 0 0 0 1px rgba(0,0,0,.06);
256    transition:transform .2s, box-shadow .25s, background .2s;
257  }
258  .donate-cta:hover{transform:translateY(-2px);box-shadow:0 16px 40px rgba(168,243,65,.36)}
259  .donate-cta .arrow{display:inline-block;transition:transform .2s}
260  .donate-cta:hover .arrow{transform:translateX(4px)}
261
262  .donate-foot{
263    text-align:center;margin-top:14px;font-size:12.5px;color:var(--mist-2);
264    letter-spacing:.02em;
265  }
266  .donate-foot b{color:var(--mist);font-weight:600}
267
268  /* ======================================================
269     Quick Donate lane — Express Checkout up by the picker.
270     Hidden by default; shown only when Stripe confirms at
271     least one wallet method is available on this device.
272     ====================================================== */
273  .quick-donate{
274    display:none;
275    margin:18px 0 14px;
276    padding:18px 18px 14px;
277    border-radius:14px;
278    background:linear-gradient(180deg,rgba(168,243,65,.07),rgba(168,243,65,.02));
279    border:1px solid rgba(168,243,65,.26);
280    position:relative;
281  }
282  .quick-donate.is-on{display:block;animation:qd-in .35s ease-out}
283  @keyframes qd-in{
284    from{opacity:0;transform:translateY(-4px)}
285    to{opacity:1;transform:none}
286  }
287  .quick-donate::before{
288    content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
289    box-shadow:inset 0 0 0 1px rgba(168,243,65,.10);
290  }
291  .qd-head{
292    display:flex;align-items:baseline;justify-content:space-between;
293    gap:12px;flex-wrap:wrap;margin-bottom:12px;
294  }
295  .qd-label{
296    display:inline-flex;align-items:center;gap:6px;
297    font-family:var(--display);font-weight:700;color:var(--leaf);
298    font-size:11.5px;letter-spacing:.2em;text-transform:uppercase;
299  }
300  .qd-label svg{width:14px;height:14px;color:var(--leaf)}
301  .qd-sub{
302    color:var(--mist);font-size:12.5px;letter-spacing:.005em;
303  }
304  .quick-donate .ch-express{margin:0;min-height:0}
305  .quick-donate .ch-express:empty{display:none}
306  .qd-or{
307    display:flex;align-items:center;text-align:center;
308    margin:12px 0 0;
309    font-family:var(--display);font-weight:700;font-size:10.5px;
310    letter-spacing:.22em;text-transform:uppercase;
310color:var(--mist-2);
311  }
312  .qd-or span{padding:0 12px;white-space:nowrap}
313  .qd-or::before,.qd-or::after{
314    content:"";flex:1;height:1px;background:rgba(255,255,255,.08);
315  }
316
317  /* ======================================================
318     Compact trust strip — sits between funds card + CTA.
319     Shows the four key reassurances + a jump link down
320     to the full security panel inside the checkout shell.
321     ====================================================== */
322  .trust-strip{
323    display:flex;align-items:center;justify-content:space-between;
324    flex-wrap:wrap;gap:14px;
325    margin:14px 0 14px;
326    padding:12px 16px;border-radius:12px;
327    background:rgba(168,243,65,.05);
328    border:1px solid rgba(168,243,65,.18);
329  }
330  .trust-strip-row{
331    display:flex;align-items:center;flex-wrap:wrap;gap:6px 14px;
332    flex:1;min-width:0;
333  }
334  .trust-strip .trust-pip{
335    display:inline-flex;align-items:center;gap:6px;
336    font-size:11.5px;color:var(--mist);
337    font-family:var(--display);letter-spacing:.005em;
338    cursor:help;
339  }
340  .trust-strip .trust-pip svg{
341    width:13px;height:13px;color:var(--leaf);flex:0 0 13px;
342  }
343  .trust-strip .trust-pip b{color:var(--bone);font-weight:600;letter-spacing:.005em}
344  .trust-strip-link{
345    display:inline-flex;align-items:center;gap:5px;
346    padding:6px 12px;border-radius:8px;
347    font-family:var(--display);font-weight:700;font-size:11px;
348    letter-spacing:.18em;text-transform:uppercase;
349    color:var(--leaf);text-decoration:none;
350    border:1px solid rgba(168,243,65,.28);
351    background:rgba(168,243,65,.04);
352    transition:background .18s, border-color .18s, transform .15s;
353    white-space:nowrap;
354  }
355  .trust-strip-link svg{width:11px;height:11px;transition:transform .2s}
356  .trust-strip-link:hover{
357    background:rgba(168,243,65,.10);
358    border-color:rgba(168,243,65,.45);
359  }
360  .trust-strip-link:hover svg{transform:translateY(2px)}
361  @media (max-width:560px){
362    .trust-strip{flex-direction:column;align-items:stretch}
363    .trust-strip-row{justify-content:center}
364    .trust-strip-link{justify-content:center}
365  }
366
367  /* Briefly highlight the full trust panel when the donor jumps to it */
368  .ch-trust-panel{transition:box-shadow .35s, border-color .35s}
369  .ch-trust-panel.is-flash{
370    border-color:rgba(168,243,65,.55) !important;
371    box-shadow:0 0 0 4px rgba(168,243,65,.10), 0 12px 36px rgba(168,243,65,.18);
372  }
373
374  /* Coordinate trail */
375  .trail-section{
376    position:relative;padding:80px var(--pad-x);isolation:isolate;overflow:hidden;
377  }
378  .trail-section::before{
379    content:"";position:absolute;inset:0;z-index:-1;
380    background:radial-gradient(50% 50% at 50% 50%, rgba(168,243,65,.10), transparent 70%);
381    filter:blur(50px);
382  }
383  .trail-wrap{max-width:1100px;margin:0 auto;position:relative;z-index:2}
384  .trail-head{text-align:center;margin-bottom:48px}
385  .trail-head h2{
386    font-family:var(--display);font-weight:800;
387    font-size:clamp(30px,4vw,52px);letter-spacing:-.02em;line-height:1.02;
388    margin:12px auto 14px;max-width:22ch;
389  }
390  .trail-head h2 em{font-style:italic;font-weight:500;color:var(--leaf)}
391  .trail-head p{max-width:54ch;margin:0 auto;color:var(--mist);font-size:16px}
392  .trail-flow{
393    position:relative;
394    display:grid;grid-template-columns:repeat(4,1fr);gap:18px;
395    align-items:start;
396  }
397  @media (max-width:880px){.trail-flow{grid-template-columns:repeat(2,1fr)}}
398  @media (max-width:520px){.trail-flow{grid-template-columns:1fr}}
399
400  .trail-line{
401    position:absolute;left:0;right:0;top:48px;height:1px;
402    pointer-events:none;z-index:0;
403  }
404  .trail-line svg{width:100%;height:2px;display:block;overflow:visible}
405  .trail-line path{
406    stroke:rgba(168,243,65,.35);stroke-width:1;
407    stroke-dasharray:3 8;fill:none;
408  }
409  @media (max-width:880px){.trail-line{display:none}}
410
411  .trail-node{
412    position:relative;z-index:1;text-align:center;
413    background:rgba(2,7,24,.55);
414    border:1px solid rgba(255,255,255,.06);
415    border-radius:18px;padding:24px 18px 22px;
416    transition:border-color .2s, transform .25s;
417  }
418  .trail-node:hover{border-color:rgba(168,243,65,.35);transform:translateY(-3px)}
419  .trail-disc{
420    width:96px;height:96px;margin:0 auto 14px;
421    border-radius:50%;
422    background:radial-gradient(circle at 50% 45%, rgba(168,243,65,.18), rgba(168,243,65,.02) 70%);
423    border:1px solid rgba(168,243,65,.4);
424    display:grid;place-items:center;
425    position:relative;
426  }
427  .trail-disc::before{
428    content:"";position:absolute;inset:-6px;border-radius:50%;
429    border:1px dashed rgba(168,243,65,.22);
430  }
431  .trail-disc .num{
432    position:absolute;top:-10px;right:-6px;
433    width:26px;height:26px;border-radius:50%;
434    background:var(--leaf);color:var(--ink);
435    font-family:var(--display);font-weight:800;font-size:12px;
436    display:grid;place-items:center;
437    box-shadow:0 4px 10px rgba(168,243,65,.4);
438  }
439  .trail-disc svg{width:36px;height:36px;color:var(--leaf);stroke-width:1.6}
440  .trail-disc .stamp{
441    position:absolute;bottom:-8px;left:50%;transform:translateX(-50%);
442    background:var(--ink);
443    border:1px solid rgba(168,243,65,.5);
444    color:var(--leaf);
445    font-family:var(--display);font-weight:800;font-size:9px;letter-spacing:.18em;
446    text-transform:uppercase;
447    padding:3px 8px;border-radius:999px;white-space:nowrap;
448  }
449  .trail-node h3{
450    font-family:var(--display);font-weight:800;font-size:18px;color:var(--bone);
451    letter-spacing:-.01em;margin:18px 0 6px;
452  }
453  .trail-node .tn-sub{color:var(--mist);font-size:13.5px;line-height:1.45;margin-bottom:10px}
454  .trail-node .tn-coord{
455    display:inline-flex;align-items:center;gap:6px;
456    font-family:var(--display);font-weight:700;font-size:10px;letter-spacing:.14em;
457    text-transform:uppercase;color:rgba(168,243,65,.85);
458  }
459  .trail-node .tn-coord::before{
460    content:"";width:6px;height:6px;border-radius:50%;background:var(--leaf);
461    box-shadow:0 0 8px var(--leaf-glow);
462  }
463
464  /* Compare flash */
465  .compare-flash-section{
466    padding:60px var(--pad-x) 80px;position:relative;isolation:isolate;
467  }
468  .compare-flash{
469    max-width:980px;margin:0 auto;
470    display:grid;grid-template-columns:1fr auto 1fr;align-items:stretch;
471    gap:0;
472    background:rgba(2,7,24,.6);
473    border:1px solid rgba(255,255,255,.07);
474    border-radius:22px;overflow:hidden;
475    box-shadow:0 20px 50px rgba(0,0,0,.35);
476  }
477  @media (max-width:680px){.compare-flash{grid-template-columns:1fr;}}
478  .cf-block{padding:36px clamp(20px,4vw,44px);text-align:center;position:relative}
479  .cf-typical{color:var(--mist);background:rgba(255,255,255,.015)}
480  .cf-eco{
481    background:linear-gradient(180deg, rgba(168,243,65,.07), rgba(168,243,65,.02));
482    position:relative;
483  }
484  .cf-eco::before{
485    content:"";position:absolute;inset:0;
486    background:radial-gradient(50% 70% at 50% 100%, rgba(168,243,65,.22), transparent 70%);
487    pointer-events:none;
488  }
489  .cf-eyebrow{
490    font-family:var(--body);font-size:11px;letter-spacing:.22em;
491    text-transform:uppercase;font-weight:700;color:var(--mist-2);margin-bottom:10px;
492  }
493  .cf-eco .cf-eyebrow{color:var(--leaf)}
494  .cf-num{
495    font-family:var(--display);font-style:italic;font-weight:500;
496    font-size:clamp(56px,9vw,104px);line-height:1;letter-spacing:-.02em;
497    color:var(--bone);
498    background:none;
499  }
500  .cf-eco .cf-num{
501    background:linear-gradient(120deg, var(--leaf) 0%, #D3FA9E 50%, var(--leaf) 100%);
502    background-size:200% 100%;
503    -webkit-background-clip:text;background-clip:text;color:transparent;
504    animation:shine 7s linear infinite;
505  }
506  .cf-label{font-family:var(--display);font-weight:700;font-size:15px;color:var(--bone);margin-top:8px}
507  .cf-sub{color:var(--mist);font-size:13px;margin-top:6px;line-height:1.5}
508  .cf-divider{
509    width:1px;background:linear-gradient(180deg, transparent, rgba(168,243,65,.3), transparent);
510    align-self:stretch;
511  }
512  @media (max-width:680px){
513    .cf-divider{
514      width:auto;height:1px;
515      background:linear-gradient(90deg, transparent, rgba(168,243,65,.3), transparent);
516    }
517  }
518
519  /* Three other ways */
520  .ways-section{padding:80px var(--pad-x) 100px;position:relative;isolation:isolate}
521  .ways-head{text-align:center;max-width:62ch;margin:0 auto 48px}
522  .ways-head h2{
523    font-family:var(--display);font-weight:800;
524    font-size:clamp(30px,4vw,48px);letter-spacing:-.02em;line-height:1.04;
525    margin:14px 0 14px;
526  }
527  .ways-head h2 em{font-style:italic;font-weight:500;color:var(--leaf)}
528  .ways-head p{color:var(--mist);font-size:16px;margin:0 auto}
529  .ways-grid{
530    max-width:1100px;margin:0 auto;
531    display:grid;grid-template-columns:repeat(3,1fr);gap:18px;
532  }
533  @media (max-width:880px){.ways-grid{grid-template-columns:1fr}}
534  .way-card{
535    display:flex;flex-direction:column;align-items:flex-start;gap:14px;
536    padding:30px 26px;border-radius:18px;
537    background:rgba(2,7,24,.55);
538    border:1px solid rgba(255,255,255,.06);
539    color:inherit;text-decoration:none;
540    transition:all .25s ease;
541    position:relative;
542  }
543  .way-card:hover{
544    border-color:rgba(168,243,65,.45);
545    transform:translateY(-3px);
546    background:rgba(2,7,24,.7);
547  }
548  .way-icon{
549    width:46px;height:46px;border-radius:12px;
550    display:grid;place-items:center;
551    background:rgba(168,243,65,.10);
552    border:1px solid rgba(168,243,65,.25);
553    color:var(--leaf);
554  }
555  .way-icon svg{width:22px;height:22px;stroke-width:1.6}
556  .way-card h3{
557    font-family:var(--display);font-weight:800;font-size:20px;
558    color:var(--bone);letter-spacing:-.01em;line-height:1.2;
559  }
560  .way-card p{color:var(--mist);font-size:14.5px;line-height:1.5;flex:1}
561  .way-arrow{
562    display:inline-flex;align-items:center;gap:8px;
563    font-family:var(--body);font-weight:600;font-size:13px;
564    color:var(--leaf);letter-spacing:.02em;
565  }
566  .way-arrow .arrow{transition:transform .25s}
567  .way-card:hover .way-arrow .arrow{transform:translateX(4px)}
568
569  /* FAQ */
570  .faq-section{padding:60px var(--pad-x) 120px;position:relative;isolation:isolate}
571  .faq-section::before{
572    content:"";position:absolute;inset:0;z-index:-1;
573    background:radial-gradient(40% 40% at 25% 50%, rgba(168,243,65,.12), transparent 70%);
574    filter:blur(60px);
575  }
576  .faq-wrap{max-width:820px;margin:0 auto;position:relative;z-index:2}
577  .faq-head{text-align:center;margin-bottom:44px}
578  .faq-head h2{
579    font-family:var(--display);font-weight:800;
580    font-size:clamp(30px,4vw,48px);letter-spacing:-.02em;line-height:1.04;
581    margin:14px 0 12px;
582  }
583  .faq-head h2 em{font-style:italic;font-weight:500;color:var(--leaf)}
584  .faq-list{display:flex;flex-direction:column;gap:10px}
585  .faq-list details{
586    background:rgba(2,7,24,.55);
587    border:1px solid rgba(255,255,255,.06);
588    border-radius:14px;
589    padding:0;
590    transition:border-color .2s, background .2s;
591  }
592  .faq-list details[open]{border-color:rgba(168,243,65,.32);background:rgba(2,7,24,.7)}
593  .faq-list summary{
594    list-style:none;cursor:pointer;
595    display:flex;align-items:center;gap:16px;
596    padding:20px 22px;
597    font-family:var(--display);font-weight:700;font-size:16.5px;color:var(--bone);
598    letter-spacing:-.005em;line-height:1.35;
599  }
600  .faq-list summary::-webkit-details-marker{display:none}
601  .faq-list summary::after{
602    content:"";flex:0 0 18px;width:18px;height:18px;margin-left:auto;
603    background:
604      linear-gradient(var(--leaf), var(--leaf)) center/12px 1.5px no-repeat,
605      linear-gradient(var(--leaf), var(--leaf)) center/1.5px 12px no-repeat;
606    transition:transform .25s;
607  }
608  .faq-list details[open] summary::after{transform:rotate(45deg)}
609  .faq-list .faq-body{
610    padding:0 22px 20px;color:var(--mist);font-size:15px;line-height:1.6;
611  }
612  .faq-list .faq-body a{color:var(--leaf);border-bottom:1px solid rgba(168,243,65,.4)}
613  .faq-list .faq-body a:hover{border-bottom-color:var(--leaf)}
614
615  /* Anchor scroll offset for in-page CTA jumps */
616  #pick{scroll-margin-top:100px}
617  #checkout{scroll-margin-top:100px}
618
619  /* =================================================
620     Inline Stripe checkout — slides in below the amount picker
621     when the visitor clicks the primary "Continue to checkout" CTA.
622     Same dark/leaf language as the rest of the page.
623     ================================================= */
624  .checkout-section{
625    position:relative;padding:20px var(--pad-x) 100px;isolation:isolate;
626    display:none; /* JS reveals on activation */
627  }
628  .checkout-section.is-open{display:block}
629  .checkout-section::before{
630    content:"";position:absolute;inset:0;z-index:-1;
631    background:radial-gradient(45% 40% at 30% 50%, rgba(168,243,65,.16), transparent 70%);
632    filter:blur(60px);pointer-events:none;
633  }
634
635  .checkout-shell{
636    max-width:760px;margin:0 auto;
637    position:relative;
638    background:rgba(2,7,24,.6);
639    border:1px solid rgba(255,255,255,.06);
640    border-radius:24px;
641    padding:clamp(28px,4vw,48px);
642    box-shadow:0 20px 60px rgba(0,0,0,.5), inset 0 0 0 1px rgba(168,243,65,.04);
643    backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
644  }
645  /* Leaf hairline at the top edge */
646  .checkout-shell::before{
647    content:"";position:absolute;top:0;left:24px;right:24px;height:1px;
648    background:linear-gradient(90deg, transparent, var(--leaf), transparent);
649    opacity:.55;
650  }
651
652  .checkout-head{
653    display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;
654    gap:12px;margin-bottom:18px;
655  }
656  .checkout-head .coord-stamp{margin:0}
657  .ch-summary{
658    font-family:var(--display);font-weight:700;
659    font-size:13px;color:var(--mist);letter-spacing:.04em;text-transform:uppercase;
660  }
661  .ch-summary .ch-sum-amt{
662    color:var(--leaf);font-weight:800;
663    text-shadow:0 0 16px rgba(168,243,65,.45);
664  }
665  .checkout-h{
666    font-family:var(--display);font-weight:800;
667    font-size:clamp(26px,3vw,38px);line-height:1.05;letter-spacing:-.02em;
668    margin-bottom:6px;color:var(--bone);
669  }
670  .checkout-h em{font-style:italic;font-weight:500;color:var(--leaf)}
671  .checkout-sub{color:var(--mist);font-size:14.5px;margin-bottom:22px}
672
673  /* Express block (Apple Pay / Google Pay / Link) */
674  .ch-express{margin-bottom:18px;min-height:0;}
675  .ch-express:empty{display:none}
676
677  .ch-divider{
678    display:flex;align-items:center;gap:14px;
679    margin:18px 0 22px;
680    font-family:var(--body);font-size:11px;letter-spacing:.22em;
681    text-transform:uppercase;color:var(--mist-2);font-weight:700;
682  }
683  .ch-divider::before,.ch-divider::after{
684    content:"";flex:1;height:1px;
685    background:linear-gradient(90deg, transparent, rgba(255,255,255,.12), transparent);
686  }
687
688  /* Form fields */
689  .ch-grid{
690    display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:14px;
691  }
692  @media (max-width:560px){.ch-grid{grid-template-columns:1fr}}
693  .ch-field{display:flex;flex-direction:column;gap:6px}
694  .ch-field.is-full{grid-column:1 / -1}
695  .ch-field label{
696    font-family:var(--body);font-size:11px;letter-spacing:.18em;
697    text-transform:uppercase;color:var(--mist-2);font-weight:700;
698  }
699  .ch-field input[type="email"],
700  .ch-field input[type="text"]{
701    background:rgba(255,255,255,.03);
702    border:1px solid rgba(255,255,255,.08);
703    border-radius:10px;
704    padding:13px 14px;
705    color:var(--bone);
706    font-family:var(--body);font-size:15px;font-weight:500;
707    transition:border-color .2s, box-shadow .2s, background .2s;
708    outline:none;
709  }
710  .ch-field input::placeholder{color:var(--mist-2)}
711  .ch-field input:focus{
712    border-color:var(--leaf);
713    background:rgba(168,243,65,.04);
714    box-shadow:0 0 0 3px rgba(168,243,65,.18);
715  }
716  .ch-field input.is-invalid{
717    border-color:var(--coral);
718    box-shadow:0 0 0 3px rgba(226,106,91,.22);
719  }
720
721  /* Card / Payment Element mount */
722  .ch-payment-wrap{
723    margin:18px 0 8px;
724    background:rgba(255,255,255,.03);
725    border:1px solid rgba(255,255,255,.08);
726    border-radius:10px;
727    padding:14px 14px;
728    transition:border-color .2s;
729  }
730  .ch-payment-wrap:focus-within{border-color:var(--leaf)}
731  .ch-payment-label{
732    display:block;
733    font-family:var(--body);font-size:11px;letter-spacing:.18em;
734    text-transform:uppercase;color:var(--mist-2);font-weight:700;
735    margin-bottom:10px;
736  }
737
738  /* Toggles row */
739  .ch-toggles{
740    display:flex;flex-direction:column;gap:10px;
741    margin:18px 0 22px;
742  }
743  .ch-toggle{
744    display:flex;align-items:center;gap:14px;
745    padding:14px 16px;
746    background:rgba(255,255,255,.025);
747    border:1px solid rgba(255,255,255,.08);
748    border-radius:12px;
749    cursor:pointer;
750    transition:border-color .2s, background .2s;
751  }
752  .ch-toggle:hover{border-color:rgba(168,243,65,.3)}
753  .ch-toggle.is-on{
754    border-color:var(--leaf);
755    background:rgba(168,243,65,.06);
756  }
757  .ch-toggle .ch-tog-knob{
758    flex:0 0 38px;width:38px;height:22px;border-radius:999px;
759    background:rgba(255,255,255,.08);
760    border:1px solid rgba(255,255,255,.12);
761    position:relative;
762    transition:background .2s, border-color .2s;
763  }
764  .ch-toggle .ch-tog-knob::after{
765    content:"";position:absolute;top:1px;left:1px;
766    width:18px;height:18px;border-radius:50%;
767    background:var(--mist);
768    transition:transform .22s, background .22s;
769  }
770  .ch-toggle.is-on .ch-tog-knob{
771    background:rgba(168,243,65,.25);
772    border-color:var(--leaf);
773  }
774  .ch-toggle.is-on .ch-tog-knob::after{
775    transform:translateX(16px);
776    background:var(--leaf);
777    box-shadow:0 0 8px var(--leaf-glow);
778  }
779  .ch-toggle .ch-tog-body{flex:1;min-width:0}
780  .ch-toggle .ch-tog-title{
781    font-family:var(--display);font-weight:700;font-size:15px;color:var(--bone);
782    line-height:1.25;
783  }
784  .ch-toggle .ch-tog-meta{color:var(--mist);font-size:13px;margin-top:2px}
785
786  /* Primary submit */
787  .ch-submit{
788    display:flex;width:100%;justify-content:center;align-items:center;gap:10px;
789    padding:18px 22px;border-radius:6px;
790    background:var(--leaf);color:var(--ink);
791    font-family:var(--display);font-weight:800;font-size:16px;letter-spacing:.005em;
792    box-shadow:0 12px 32px rgba(168,243,65,.28), inset 0 0 0 1px rgba(0,0,0,.06);
793    transition:transform .2s, box-shadow .25s, background .2s, opacity .2s;
794    cursor:pointer;
795  }
796  .ch-submit:hover:not(:disabled){
797    transform:translateY(-2px);
798    box-shadow:0 16px 40px rgba(168,243,65,.38);
799  }
800  .ch-submit:disabled{opacity:.55;cursor:not-allowed;transform:none}
801  .ch-submit .arrow{display:inline-block;transition:transform .2s}
802  .ch-submit:hover:not(:disabled) .arrow{transform:translateX(4px)}
803  .ch-submit .spinner{
804    width:16px;height:16px;border-radius:50%;
805    border:2px solid rgba(2,7,24,.25);
806    border-top-color:var(--ink);
807    animation:spin .8s linear infinite;
808    display:none;
809  }
810  .ch-submit.is-loading .spinner{display:inline-block}
811  .ch-submit.is-loading .arrow{display:none}
812  @keyframes spin{to{transform:rotate(360deg)}}
813  @media (prefers-reduced-motion:reduce){
814    .ch-submit{transition:none}
815    .ch-submit:hover:not(:disabled){transform:none}
816    .ch-submit .spinner{animation-duration:1.4s}
817  }
818
819  /* Error message */
820  .ch-error{
821    margin-top:14px;padding:12px 14px;border-radius:10px;
822    background:rgba(226,106,91,.10);
823    border:1px solid rgba(226,106,91,.35);
824    color:#f5c2bb;font-size:13.5px;line-height:1.45;
825    display:none;
826  }
827  .ch-error.is-visible{display:block}
828
829  /* Footer line */
830  .ch-foot{
831    text-align:center;margin-top:18px;
832    font-size:12.5px;
832color:var(--mist-2);letter-spacing:.02em;line-height:1.6;
833  }
834  .ch-foot b{color:var(--mist);font-weight:600}
835  .ch-foot .ch-stamp{
836    display:inline-flex;align-items:center;gap:6px;
837    color:var(--leaf);font-family:var(--display);font-weight:700;font-size:11px;
838    letter-spacing:.18em;text-transform:uppercase;margin-bottom:8px;
839  }
840  .ch-foot .ch-stamp::before{content:"⊕";font-size:13px}
841
842  /* =====================================================
843     Donor confidence — trust signals around the form
844     ===================================================== */
845  .ch-trust-hero{
846    display:flex;align-items:center;gap:14px;
847    padding:14px 18px;border-radius:14px;
848    border:1px solid rgba(168,243,65,.22);
849    background:linear-gradient(180deg,rgba(168,243,65,.06),rgba(168,243,65,.02));
850    margin:0 0 22px;
851  }
852  .ch-trust-hero .ch-lock{
853    width:38px;height:38px;flex:0 0 38px;border-radius:50%;
854    display:flex;align-items:center;justify-content:center;
855    background:rgba(168,243,65,.12);color:var(--leaf);
856    box-shadow:inset 0 0 0 1px rgba(168,243,65,.35);
857  }
858  .ch-trust-hero .ch-lock svg{width:18px;height:18px}
859  .ch-trust-hero .ch-th-body{flex:1;min-width:0}
860  .ch-trust-hero .ch-th-title{
861    font-family:var(--display);font-weight:700;color:var(--bone);
862    font-size:14px;letter-spacing:-.005em;
863  }
864  .ch-trust-hero .ch-th-meta{
865    color:var(--mist);font-size:12.5px;margin-top:2px;letter-spacing:.005em;
866  }
867  .ch-trust-hero .ch-th-meta b{color:var(--leaf);font-weight:700;letter-spacing:.04em}
868  .ch-trust-hero .ch-stripe-mark{
869    display:inline-flex;align-items:center;gap:6px;flex-shrink:0;
870    padding:6px 10px;border-radius:8px;
871    border:1px solid rgba(255,255,255,.10);
872    background:rgba(255,255,255,.04);
873    color:var(--mist);font-family:var(--display);font-weight:700;
874    font-size:10.5px;letter-spacing:.16em;text-transform:uppercase;
875  }
876  .ch-stripe-mark::before{
877    content:"";width:14px;height:14px;border-radius:3px;
878    background:linear-gradient(135deg,#635bff 0%,#7a73ff 100%);
879    box-shadow:0 0 0 1px rgba(99,91,255,.35);
880  }
881  @media (max-width:560px){
882    .ch-trust-hero{flex-wrap:wrap}
883    .ch-trust-hero .ch-stripe-mark{order:3;margin-top:4px}
884  }
885
886  /* Below-submit trust grid */
887  .ch-trust-panel{
888    margin-top:22px;padding:22px;border-radius:18px;
889    border:1px solid rgba(255,255,255,.08);
890    background:linear-gradient(180deg,rgba(255,255,255,.025),rgba(255,255,255,.005));
891  }
892  .ch-trust-panel-head{
893    display:flex;align-items:baseline;justify-content:space-between;
894    gap:14px;flex-wrap:wrap;margin-bottom:14px;
895  }
896  .ch-trust-panel-head .lbl{
897    font-family:var(--display);font-weight:700;color:var(--leaf);
898    font-size:10.5px;letter-spacing:.22em;text-transform:uppercase;
899  }
900  .ch-trust-panel-head .lbl::before{content:"⊕  ";opacity:.9}
901  .ch-trust-panel-head .num{
902    font-family:var(--display);font-weight:700;color:var(--bone);
903    font-size:14px;letter-spacing:.01em;
904  }
905  .ch-trust-panel-head .num b{color:var(--leaf)}
906  .ch-trust-grid{
907    display:grid;grid-template-columns:repeat(2,1fr);gap:12px 18px;
908  }
909  @media (max-width:560px){.ch-trust-grid{grid-template-columns:1fr}}
910  .ch-trust-item{
911    display:flex;gap:10px;align-items:flex-start;
912    padding:10px 0;
913  }
914  .ch-trust-item + .ch-trust-item{
915    border-top:1px solid rgba(255,255,255,.04);
916  }
917  @media (min-width:561px){
918    .ch-trust-grid > .ch-trust-item:nth-child(2){border-top:none}
919  }
920  .ch-trust-item .ic{
921    width:28px;height:28px;flex:0 0 28px;border-radius:8px;
922    display:flex;align-items:center;justify-content:center;
923    background:rgba(168,243,65,.10);color:var(--leaf);
924    box-shadow:inset 0 0 0 1px rgba(168,243,65,.22);
925  }
926  .ch-trust-item .ic svg{width:14px;height:14px}
927  .ch-trust-item .bd{flex:1;min-width:0}
928  .ch-trust-item .bd-t{
929    font-family:var(--display);font-weight:700;color:var(--bone);
930    font-size:13px;letter-spacing:-.003em;line-height:1.3;
931  }
932  .ch-trust-item .bd-s{
933    color:var(--mist-2);font-size:12px;margin-top:2px;line-height:1.45;
934  }
935  .ch-trust-item .bd-s b{color:var(--mist);font-weight:600}
936  .ch-trust-item .bd-s a{color:var(--leaf);text-decoration:none;border-bottom:1px solid rgba(168,243,65,.3)}
937
938  /* Charity legal strip */
939  .ch-legal{
940    margin-top:18px;padding-top:16px;
941    border-top:1px solid rgba(255,255,255,.06);
942    display:grid;grid-template-columns:1fr 1fr;gap:14px;
943    font-size:11.5px;
943color:var(--mist-2);line-height:1.55;
944  }
945  @media (max-width:560px){.ch-legal{grid-template-columns:1fr}}
946  .ch-legal .ch-leg-h{
947    display:block;font-family:var(--display);font-weight:700;
948    color:var(--bone);font-size:11.5px;letter-spacing:.06em;
949    text-transform:uppercase;margin-bottom:3px;
950  }
951  .ch-legal b{color:var(--leaf);font-weight:700;letter-spacing:.04em}
952  .ch-legal a{color:var(--mist);text-decoration:none;border-bottom:1px solid rgba(245,243,238,.18)}
953
954  /* Pass-through impact strip */
955  .ch-impact-strip{
956    margin-top:18px;padding:14px 18px;border-radius:12px;
957    background:rgba(168,243,65,.06);
958    border:1px solid rgba(168,243,65,.18);
959    text-align:center;font-size:12.5px;color:var(--mist);
960    line-height:1.55;
961  }
962  .ch-impact-strip .ch-imp-num{
963    display:block;font-family:var(--display);font-weight:800;
964    color:var(--leaf);font-size:22px;letter-spacing:-.01em;line-height:1.1;
965    margin-bottom:4px;
966  }
967  .ch-impact-strip b{color:var(--bone);font-weight:600}
968
969  /* Cinema band */
970  .cinema-band{position:relative;height:60vh;min-height:420px;overflow:hidden;isolation:isolate}
971  .cinema-band-img{position:absolute;inset:0;background-size:cover;background-position:center;background-attachment:fixed}
972  @media (max-width:760px){.cinema-band-img{background-attachment:scroll}.cinema-band{height:45vh;min-height:280px}}
973  .cinema-band::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgba(2,7,24,.22),rgba(2,7,24,.08) 50%,rgba(2,7,24,.34));pointer-events:none}
974  .cinema-band-caption{position:absolute;left:0;right:0;bottom:36px;text-align:center;z-index:2;font-family:var(--display);font-weight:500;font-style:italic;font-size:clamp(20px,2.2vw,28px);color:var(--bone);max-width:48ch;margin:0 auto;padding:0 var(--pad-x);letter-spacing:-.005em}
975  .cinema-band-caption .cs{display:inline-flex;align-items:center;gap:8px;margin-top:14px;font-style:normal;font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:var(--leaf);font-weight:700}
976  .cinema-band-caption .cs::before{content:"⊕";font-size:12px}
977</style>
978<!-- seo:auto -->
979<meta property="og:type" content="website" />
980<meta property="og:site_name" content="Ecoflix Foundation" />
981<meta property="og:title" content="Donate to Wildlife Conservation · Ecoflix Foundation" />
982<meta property="og:description" content="100% of every donation reaches the partner NGO doing the work. Pick an amount and see exactly what it funds. Ecoflix Foundation takes nothing from your gift." />
983<meta property="og:url" content="https://www.ecoflix.com/donate/" />
984<meta property="og:image" content="https://www.ecoflix.com/assets/og-cover.png" />
985<meta name="twitter:card" content="summary_large_image" />
986<meta name="twitter:title" content="Donate to Wildlife Conservation · Ecoflix Foundation" />
987<meta name="twitter:description" content="100% of every donation reaches the partner NGO doing the work. Pick an amount and see exactly what it funds. Ecoflix Foundation takes nothing from your gift." />
988<meta name="twitter:image" content="https://www.ecoflix.com/assets/og-cover.png" />
989<script type="application/ld+json">{"@context":"https://schema.org","@type":["NGO","Organization"],"name":"Ecoflix Foundation","alternateName":"Ecoflix","url":"https://www.ecoflix.com/","logo":"https://www.ecoflix.com/assets/logo.png","description":"The not-for-profit foundation behind the Ecoflix channel. We fund the NGOs on the front line of wildlife conservation.","sameAs":["https://www.instagram.com/ecoflixofficial/","https://www.facebook.com/ecoflixofficial","https://twitter.com/ecoflixofficial","https://www.youtube.com/@ecoflixofficial","https://www.linkedin.com/company/ecoflix/","https://www.tiktok.com/@ecoflixofficial"]}</script>
989
990<!-- /seo:auto -->
991</head>
992<body data-i18n-no-bar>
993
994<a href="#main" class="skip-to-content">Skip to content</a>
995<button class="bttop" id="backToTop" type="button" aria-label="Back to top">
996  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 19V5M5 12l7-7 7 7"/></svg>
997</button>
998
999<!-- Header -->
1000<header class="header" id="header">
1001  <a href="/" class="brand" aria-label="Ecoflix home"><img src="/assets/logo.png" alt="Ecoflix" style="height:34px;width:auto;display:block" /></a>
1002  <nav class="nav" aria-label="Main">
1003    <a href="/mission/">Mission</a>
1004    <a href="/how-we-help/">How we help</a>
1005    <a href="/projects/">Projects</a>
1006    <a href="/ngo-partners/">NGO Partners</a>
1007    <a href="/watch/">Watch</a>
1008    <a href="/news/">News</a>
1009  <a href="/features/">Features</a></nav>
1010  <div class="header-cta">
1011    <a class="btn btn-ghost" href="https://watch.ecoflix.com/" data-i18n="cta.channel">Channel <span class="arrow">→</span></a>
1012    <a class="btn btn-primary" href="#pick">Donate <span class="arrow">→</span></a>
1013  </div>
1014  <button class="menu-btn" id="menuBtn" aria-label="Open menu" aria-expanded="false" aria-controls="drawer"><span></span><span></span><span></span></button>
1015</header>
1016<div class="drawer" id="drawer" aria-hidden="true">
1017  <span class="drawer-eyebrow">Menu</span>
1018  <nav class="drawer-nav" aria-label="Mobile">
1019    <a href="/mission/"><span class="drawer-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="1.5" fill="currentColor" stroke="none"/></svg></span>Mission<span class="drawer-arrow" aria-hidden="true">→</span></a>
1020    <a href="/how-we-help/"><span class="drawer-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M3 12h4l3-7 4 14 3-7h4"/></svg></span>How we help<span class="drawer-arrow" aria-hidden="true">→</span></a>
1021    <a href="/projects/"><span class="drawer-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M12 2C8 2 5 5 5 9c0 5.5 7 13 7 13s7-7.5 7-13c0-4-3-7-7-7z"/><circle cx="12" cy="9" r="2.5"/></svg></span>Projects<span class="drawer-arrow" aria-hidden="true">→</span></a>
1022    <a href="/watch/"><span class="drawer-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M10 9l5 3-5 3z" fill="currentColor" stroke="none"/></svg></span>Watch<span class="drawer-arrow" aria-hidden="true">→</span></a>
1023    <a href="/news/"><span class="drawer-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 8h10M7 12h10M7 16h6"/></svg></span>News<span class="drawer-arrow" aria-hidden="true">→</span></a>
1024    <a href="/features/"><span class="drawer-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M4 5h16M4 10h16M4 15h10"/></svg></span>Features<span class="drawer-arrow" aria-hidden="true">→</span></a>
1025  </nav>
1026  <a class="btn btn-primary" href="/donate/">Support our work <span class="arrow">→</span></a>
1027  <div class="drawer-footer"><span class="dot"></span>100% of every donation reaches the project.</div>
1028</div>
1029
1030
1031<main id="main">
1032
1033  <!-- =================================================
1034       HERO
1035       ================================================= -->
1036  <section class="pg-hero">
1037    <div class="bg-cartograph" aria-hidden="true">
1038      <svg viewBox="0 0 1600 900" preserveAspectRatio="xMidYMid slice">
1039        <line class="bg-lat" x1="-50" y1="220" x2="1650" y2="220"/>
1040        <line class="bg-lat" x1="-50" y1="440" x2="1650" y2="440"/>
1041        <line class="bg-lat" x1="-50" y1="660" x2="1650" y2="660"/>
1042        <path class="bg-trail" d="M-40,520 C260,440 540,580 800,520 C1060,460 1340,580 1660,520"/>
1043        <path class="bg-trail-soft" d="M-30,640 C200,510 420,420 700,440 C920,460 1080,360 1280,300 C1450,260 1560,220 1700,200"/>
1044        <circle class="bg-track" cx="260" cy="500" r="3"/>
1045        <circle class="bg-track" cx="360" cy="520" r="3"/>
1046        <circle class="bg-track" cx="460" cy="540" r="3"/>
1047        <circle class="bg-track" cx="560" cy="548" r="3"/>
1048        <g class="bg-compass" transform="translate(1420,200)">
1049          <circle class="bg-compass-ring" r="46"/>
1050          <circle class="bg-compass-ring" r="32"/>
1051          <line class="bg-compass-tick" x1="0" y1="-50" x2="0" y2="50"/>
1052          <line class="bg-compass-tick" x1="-50" y1="0" x2="50" y2="0"/>
1053          <line class="bg-compass-tick" x1="-36" y1="-36" x2="36" y2="36"/>
1054          <line class="bg-compass-tick" x1="36" y1="-36" x2="-36" y2="36"/>
1055          <polygon class="bg-compass-arrow" points="0,-46 6,0 0,46 -6,0"/>
1056          <polygon class="bg-compass-arrow" points="-46,0 0,6 46,0 0,-6"/>
1057          <text class="bg-compass-label" x="0" y="-56" text-anchor="middle">N</text>
1058          <text class="bg-compass-label" x="58" y="4" text-anchor="middle">E</text>
1059          <text class="bg-compass-label" x="0" y="68" text-anchor="middle">S</text>
1060          <text class="bg-compass-label" x="-58" y="4" text-anchor="middle">W</text>
1061        </g>
1062        <g transform="translate(140,80)">
1063          <text class="bg-coord-text" x="0" y="0">GIVE · ROUTE · IMPACT</text>
1064        </g>
1065        <g transform="translate(140,820)">
1066          <text class="bg-coord-text" x="0" y="0">100% PASS-THROUGH · NO FEES TAKEN</text>
1067        </g>
1068        <g class="bg-marker" transform="translate(1180,480)">
1069          <circle r="14"/><circle r="5" class="bg-marker-dot"/>
1070        </g>
1071      </svg>
1072    </div>
1073    <span class="eyebrow" style="display:inline-flex">Give → Impact</span>
1074    <h1>Your donation lands <em>where it matters.</em></h1>
1075    <p class="lede">100% of every donation routes straight to the partner NGO doing the work. We take nothing. Pick an amount, then watch what it funds.</p>
1076  </section>
1077
1078  <!-- =================================================
1079       AMOUNT PICKER
1080       ================================================= -->
1081  <section class="donate-section" id="pick">
1082    <div class="donate-shell">
1083
1084      <!-- Contextual locale card — populated by i18n.js on boot.
1085           Shows the donor's detected city, country flag, lat/lon and
1086           the currency we've converted amounts into. Click "Use USD"
1087           to flip back to the default. -->
1088      <aside class="dp-locale is-default" id="dpLocale" aria-label="Your detected location and currency">
1089        <span class="dpl-eyebrow">Showing for</span>
1090        <span class="dpl-flag-line"><span class="flag" id="dpLocaleFlag" aria-hidden="true">🌐</span><span id="dpLocaleCountry">Detecting…</span></span>
1091        <span class="dpl-where" id="dpLocaleWhere">—</span>
1092        <span class="dpl-coord" id="dpLocaleCoord">— · —</span>
1093        <span class="dpl-ccy"><span class="dpl-ccy-note">Currency</span> <span id="dpLocaleCcy">USD</span></span>
1094        <a class="dpl-toggle" id="dpLocaleToggle" href="#" role="button">Use English · USD</a>
1095      </aside>
1096
1097      <span class="coord-stamp" aria-label="Coordinates">
1098        <span class="cs-lat">⊕ DEPARTURE</span><span class="cs-sep"></span><span class="cs-lon">03.4°N 18.5°E</span>
1099      </span>
1100      <h2 class="donate-h">Pick your <em>amount.</em></h2>
1101      <p class="donate-sub">Every figure below is mapped to a real, line-item piece of conservation work.</p>
1102
1103      <!-- Frequency toggle -->
1104      <div class="freq-toggle" role="tablist" aria-label="Donation frequency">
1105        <button type="button" role="tab" data-freq="once" class="is-on" id="freqOnce" aria-selected="true">One-time</button>
1106        <button type="button" role="tab" data-freq="monthly" id="freqMonthly" aria-selected="false">Monthly · build a habit</button>
1107      </div>
1108
1109      <!-- Preset amounts -->
1110      <div class="amount-grid" role="radiogroup" aria-label="Donation amount">
1111        <button type="button" role="radio" class="amount-tile" data-amt="25" aria-checked="false">
1112          <span class="at-curr">$</span>25<span class="at-suf">Patrol week</span>
1113        </button>
1114        <button type="button" role="radio" class="amount-tile is-on" data-amt="50" aria-checked="true">
1115          <span class="at-curr">$</span>50<span class="at-suf">Most popular</span>
1116        </button>
1117        <button type="button" role="radio" class="amount-tile" data-amt="100" aria-checked="false">
1118          <span class="at-curr">$</span>100<span class="at-suf">Field kit</span>
1119        </button>
1120        <button type="button" role="radio" class="amount-tile" data-amt="250" aria-checked="false">
1121          <span class="at-curr">$</span>250<span class="at-suf">Reef week</span>
1122        </button>
1123      </div>
1124
1125      <!-- Custom amount (typed value is always USD; label says so when the
1126           visitor's display currency isn't USD, so £/$ never sit unexplained
1127           side by side) -->
1128      <div class="custom-row">
1129        <label for="customAmt" id="customAmtLabel">Custom</label>
1130        <span class="cs-curr">$</span>
1131        <input type="number" min="1" inputmode="numeric" id="customAmt" placeholder="Other amount" aria-label="Custom donation amount in US dollars" />
1132      </div>
1133
1134      <!-- Currency note — i18n.js reveals this if the visitor is on a
1135           non-USD locale, so they know amounts are shown locally but
1136           the card is charged in USD. -->
1137      <p id="currencyNote" class="currency-note" data-i18n="currency.note" style="display:none">
1138        Donations are charged in US dollars; your bank converts at the day's rate.
1139      </p>
1140
1141      <!-- Live impact line -->
1142      <div class="funds-card" id="fundsCard" aria-live="polite">
1143        <span class="funds-icon" aria-hidden="true">
1144          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M12 21s-7-4.5-7-11a5 5 0 0 1 9-3 5 5 0 0 1 9 3c0 6.5-7 11-7 11z"/></svg>
1145        </span>
1146        <div class="funds-text">
1147          <div class="funds-eyebrow" id="fundsEyebrow">What this funds at $50</div>
1148          <div class="funds-line" id="fundsLine">One rescued animal's first month of sanctuary care.</div>
1149        </div>
1150      </div>
1151
1152      <!-- =================================================
1153           QUICK DONATE — Apple Pay / Google Pay / Link
1154           Renders one-tap wallet buttons next to the picker.
1155           Stripe boots on page load and the Express Checkout
1156           Element only paints if the device actually supports
1157           a wallet — if not, this whole block stays hidden
1158           and the visitor uses Continue-to-checkout below.
1159           ================================================= -->
1160      <div class="quick-donate" id="quickDonateZone" aria-hidden="true">
1161        <div class="qd-head">
1162          <span class="qd-label">
1163            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M13 2L3 14h7v8l11-13h-8z"/></svg>
1164            Quick donate
1165          </span>
1166          <span class="qd-sub">One tap. No forms. Your wallet fills in the rest.</span>
1167        </div>
1168        <div class="ch-express" id="expressCheckoutElement" aria-label="One-tap wallet payment"></div>
1169        <div class="qd-or" id="qdOr" aria-hidden="true"><span>or pay your way</span></div>
1170      </div>
1171
1172      <!-- Compact trust strip — visible BEFORE the donor clicks Continue.
1173           Mirrors the four most critical reassurances from the full
1174           panel below the form, plus a jump link to the full detail. -->
1175      <div class="trust-strip" role="note" aria-label="Donation security at a glance">
1176        <span class="trust-strip-row">
1177          <span class="trust-pip" title="Stripe — same processor used by Amazon, Shopify, Target">
1178            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>
1179            <b>Stripe</b>
1180          </span>
1181          <span class="trust-pip" title="256-bit TLS · PCI DSS Level 1">
1182            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3l8 3v6c0 4.5-3.4 8.4-8 9-4.6-.6-8-4.5-8-9V6l8-3z"/><path d="M9 12l2 2 4-4"/></svg>
1183            <b>Bank-grade encryption</b>
1184          </span>
1185          <span class="trust-pip" title="US 501(c)(3) EIN 65-1212560 · UK charity 1200441">
1186            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 21h18"/><path d="M5 21V10l7-5 7 5v11"/><path d="M10 21v-6h4v6"/></svg>
1187            <b>Registered charity</b>
1188          </span>
1189          <span class="trust-pip" title="Tax receipt arrives in seconds, cancel any recurring gift anytime">
1190            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/></svg>
1191            <b>Instant receipt</b>
1192          </span>
1193        </span>
1194        <a href="#trust-panel" class="trust-strip-link" id="trustStripLink">
1195          Read full security details
1196          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14"/><path d="M5 12l7 7 7-7"/></svg>
1197        </a>
1198      </div>
1199
1200      <!-- CTA — now opens the on-page Stripe checkout below -->
1201      <button type="button" class="donate-cta" id="donateCta" aria-controls="checkout" aria-expanded="false">
1202        <span id="ctaLabel">Continue to checkout</span>
1203        <span class="arrow" aria-hidden="true">→</span>
1204      </button>
1205      <div class="donate-foot">
1206        Secure checkout. Apple Pay, Google Pay, card or bank. Cancel anytime. Tax-deductible in the US (501c3) and UK (registered charity) · receipt emailed instantly.
1207      </div>
1208    </div>
1209  </section>
1210
1211  <!-- =================================================
1212       INLINE STRIPE CHECKOUT
1213       Mounts after the visitor clicks "Continue to checkout".
1214       Reads the selected amount + frequency from the picker above
1215       in real time — no page navigation.
1216       ================================================= -->
1217  <section class="checkout-section" id="checkout" aria-labelledby="checkoutH" aria-hidden="true">
1218    <div class="checkout-shell" role="region" tabindex="-1">
1219      <div class="checkout-head">
1220        <span class="coord-stamp" aria-label="Checkout coordinates">
1221          <span class="cs-lat">⊕ CHECKOUT</span><span class="cs-sep"></span><span class="cs-lon">SECURE · STRIPE</span>
1222        </span>
1223        <span class="ch-summary" id="chSummary">
1224          You're giving <span class="ch-sum-amt" id="chSumAmt">$50</span> · one-time
1225        </span>
1226      </div>
1227
1228      <h2 class="checkout-h" id="checkoutH">One last <em>step.</em></h2>
1229      <p class="checkout-sub">Apple Pay, Google Pay, card or bank — pick whatever's fastest. Your receipt arrives in seconds.</p>
1230
1231      <!-- Donor confidence — pre-form reassurance bar -->
1232      <div class="ch-trust-hero" role="note" aria-label="Payment security">
1233        <span class="ch-lock" aria-hidden="true">
1234          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
1235            <rect x="4" y="11" width="16" height="10" rx="2"/>
1236            <path d="M8 11V7a4 4 0 0 1 8 0v4"/>
1237            <circle cx="12" cy="16" r="1.2" fill="currentColor"/>
1238          </svg>
1239        </span>
1240        <span class="ch-th-body">
1241          <span class="ch-th-title">Secure payment — your card details never touch our servers.</span>
1242          <span class="ch-th-meta"><b>256-bit TLS</b> · <b>PCI DSS Level 1</b> certified · processed end-to-end by Stripe.</span>
1243        </span>
1244        <span class="ch-stripe-mark" aria-label="Powered by Stripe">Powered by Stripe</span>
1245      </div>
1246
1247      <!-- Express checkout (Apple Pay / Google Pay / Link) has been
1248           hoisted up next to the amount picker as the Quick Donate lane,
1249           so wallet users tap once and never see this form. -->
1250      <div class="ch-divider" id="chDivider"><span>pay with card or bank</span></div>
1251
1252      <!-- Donor details -->
1253      <div class="ch-grid">
1254        <div class="ch-field is-full">
1255          <label for="donorEmail">Email · for your receipt</label>
1256          <input type="email" id="donorEmail" name="email" autocomplete="email" required placeholder="[email protected]" aria-required="true" />
1257        </div>
1258        <div class="ch-field">
1259          <label for="donorName">Name on receipt <span style="text-transform:none;letter-spacing:0;color:var(--mist-2);font-weight:500">(optional)</span></label>
1260          <input type="text" id="donorName" name="name" autocomplete="name" placeholder="Your name" />
1261        </div>
1262        <div class="ch-field">
1263          <label for="donorDedication">Dedication <span style="text-transform:none;letter-spacing:0;color:var(--mist-2);font-weight:500">(optional)</span></label>
1264          <input type="text" id="donorDedication" name="dedication" placeholder="In memory of …" maxlength="120" />
1265        </div>
1266      </div>
1267
1268      <!-- Payment Element (card / wallets / bank) -->
1269      <div class="ch-payment-wrap">
1270        <span class="ch-payment-label">Payment details</span>
1271        <div id="paymentElement"><!-- Stripe Payment Element mounts here --></div>
1272      </div>
1273
1274      <!-- Toggles -->
1275      <div class="ch-toggles">
1276        <div class="ch-toggle" id="togMonthly" role="switch" aria-checked="false" tabindex="0">
1277          <span class="ch-tog-knob" aria-hidden="true"></span>
1278          <span class="ch-tog-body">
1279            <span class="ch-tog-title">Make it monthly</span>
1280            <span class="ch-tog-meta" id="togMonthlyMeta">$50 / month, recurring. Cancel anytime.</span>
1281          </span>
1282        </div>
1283        <div class="ch-toggle" id="togCoverFee" role="switch" aria-checked="false" tabindex="0">
1284          <span class="ch-tog-knob" aria-hidden="true"></span>
1285          <span class="ch-tog-body">
1286            <span class="ch-tog-title">Cover the card processing fee</span>
1287            <span class="ch-tog-meta" id="togCoverFeeMeta">+$1.75 — keeps 100% of your gift reaching the field.</span>
1288          </span>
1289        </div>
1290      </div>
1291
1292      <!-- Error message -->
1293      <div class="ch-error" id="chError" role="alert" aria-live="assertive"></div>
1294
1295      <!-- Submit -->
1296      <button type="button" class="ch-submit" id="chSubmit" disabled>
1297        <span class="spinner" aria-hidden="true"></span>
1298        <span id="chSubmitLabel">DONATE $50</span>
1299        <span class="arrow" aria-hidden="true">→</span>
1300      </button>
1301
1302      <div class="ch-foot">
1303        <span class="ch-stamp">100% reaches the NGO · We take nothing</span><br/>
1304        Click <b>Donate</b> and Stripe will charge your card. You'll be redirected to a thank-you page and your receipt will arrive within seconds.
1305      </div>
1306
1307      <!-- =================================================
1308           DONOR CONFIDENCE — trust panel
1309           Visible to every donor at the moment of giving.
1310           ================================================= -->
1311      <div class="ch-trust-panel" id="trust-panel" role="region" aria-label="Why you can trust this donation" tabindex="-1">
1312        <div class="ch-trust-panel-head">
1313          <span class="lbl">Why you can give with confidence</span>
1314          <span class="num">A real charity. A real audit trail.</span>
1315        </div>
1316
1317        <div class="ch-trust-grid">
1318          <!-- 1. Payment processor -->
1319          <div class="ch-trust-item">
1320            <span class="ic" aria-hidden="true">
1321              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>
1322            </span>
1323            <span class="bd">
1324              <span class="bd-t">Stripe handles every keystroke</span>
1325              <span class="bd-s">Card details are encrypted in your browser and sent straight to <b>Stripe</b> — Ecoflix never sees, stores or has access to your card number. Same processor Amazon, Shopify and Target use.</span>
1326            </span>
1327          </div>
1328
1329          <!-- 2. PCI / compliance -->
1330          <div class="ch-trust-item">
1331            <span class="ic" aria-hidden="true">
1332              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l8 3v6c0 4.5-3.4 8.4-8 9-4.6-.6-8-4.5-8-9V6l8-3z"/><path d="M9 12l2 2 4-4"/></svg>
1333            </span>
1334            <span class="bd">
1335              <span class="bd-t">Bank-grade encryption</span>
1336              <span class="bd-s"><b>256-bit TLS</b> in transit · <b>PCI DSS Level 1</b> (the highest tier) · 3-D Secure / SCA on every card, so your bank confirms the charge with you directly.</span>
1337            </span>
1338          </div>
1339
1340          <!-- 3. Registered charity -->
1341          <div class="ch-trust-item">
1342            <span class="ic" aria-hidden="true">
1343              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21h18"/><path d="M5 21V10l7-5 7 5v11"/><path d="M10 21v-6h4v6"/></svg>
1344            </span>
1345            <span class="bd">
1346              <span class="bd-t">A registered charity, twice over</span>
1347              <span class="bd-s">US <b>501(c)(3)</b> · EIN <b>65-1212560</b><br/>UK Registered Charity <b>1200441</b>. Donations are tax-deductible — receipts qualify for IRS write-off and UK Gift Aid.</span>
1348            </span>
1349          </div>
1350
1351          <!-- 4. Receipt promise -->
1352          <div class="ch-trust-item">
1353            <span class="ic" aria-hidden="true">
1354              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/></svg>
1355            </span>
1356            <span class="bd">
1357              <span class="bd-t">Receipt within seconds</span>
1358              <span class="bd-s">Stripe emails a tax-ready receipt the moment your card clears — to the address you enter above. Keep it for your records; you'll need it at tax time.</span>
1359            </span>
1360          </div>
1361
1362          <!-- 5. Cancel anytime -->
1363          <div class="ch-trust-item">
1364            <span class="ic" aria-hidden="true">
1365              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 3-6.7"/><path d="M3 4v5h5"/></svg>
1366            </span>
1367            <span class="bd">
1368              <span class="bd-t">Cancel a recurring gift anytime</span>
1369              <span class="bd-s">If you make it monthly, you can pause, change the amount or cancel from your Stripe customer portal (link arrives with your first receipt) or by emailing us — same business day, no friction.</span>
1370            </span>
1371          </div>
1372
1373          <!-- 6. Real humans -->
1374          <div class="ch-trust-item">
1375            <span class="ic" aria-hidden="true">
1376              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 11.5a8.4 8.4 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.4 8.4 0 0 1-3.8-.9L3 21l1.9-5.7a8.4 8.4 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.4 8.4 0 0 1 3.8-.9h.5a8.5 8.5 0 0 1 8 8z"/></svg>
1377            </span>
1378            <span class="bd">
1379              <span class="bd-t">Questions? A real person replies.</span>
1380              <span class="bd-s">Email <a href="mailto:[email protected]">[email protected]</a> — most messages get a reply within a few hours. No bots, no call centres, no "ticket numbers".</span>
1381            </span>
1382          </div>
1383        </div>
1384
1385        <!-- Pass-through proof -->
1386        <div class="ch-impact-strip">
1387          <span class="ch-imp-num">$698K routed to 64 NGOs so far</span>
1388          <b>100% pass-through.</b> Ecoflix Foundation covers its own overheads through the streaming subscription — every cent you give walks straight to the partner NGO on the receipt.
1389        </div>
1390
1391        <!-- Charity legal strip -->
1392        <div class="ch-legal">
1393          <div>
1394            <span class="ch-leg-h">United States</span>
1395            Ecoflix Foundation Inc. — <b>501(c)(3)</b> public charity<br/>
1396            EIN <b>65-1212560</b> · <a href="/mission/">View our IRS determination letter</a>
1397          </div>
1398          <div>
1399            <span class="ch-leg-h">United Kingdom</span>
1400            Ecoflix Foundation UK — Registered Charity <b>1200441</b><br/>
1401            Regulated by the Charity Commission · <a href="https://register-of-charities.charitycommission.gov.uk/" rel="nofollow" target="_blank">verify on the public register</a>
1402          </div>
1403        </div>
1404      </div>
1405    </div>
1406  </section>
1407
1408  <!-- =================================================
1409       COORDINATE TRAIL — where the money goes
1410       ================================================= -->
1411  <section class="trail-section section-lift-soft">
1412    <div class="bg-cartograph is-mirror" aria-hidden="true">
1413      <svg viewBox="0 0 1600 900" preserveAspectRatio="xMidYMid slice">
1414        <line class="bg-lat" x1="-50" y1="180" x2="1650" y2="180"/>
1415        <line class="bg-lat" x1="-50" y1="720" x2="1650" y2="720"/>
1416        <path class="bg-trail-soft" d="M-40,180 C260,260 540,140 800,200 C1060,260 1340,140 1660,200"/>
1417        <g transform="translate(140,60)">
1418          <text class="bg-coord-text" x="0" y="0">02 · ROUTING</text>
1419        </g>
1420      </svg>
1421    </div>
1422    <div class="trail-wrap">
1423      <div class="trail-head">
1424        <span class="eyebrow" style="display:inline-flex;margin:0 auto">The pass-through</span>
1425        <h2>Watch the money <em>walk.</em></h2>
1426        <p>From your card to a partner NGO to a real animal in a real place. No middle handlers, no overhead skim.</p>
1427      </div>
1428
1429      <div class="trail-flow">
1430        <!-- Dashed connecting line behind the nodes (desktop only) -->
1431        <div class="trail-line" aria-hidden="true">
1432          <svg viewBox="0 0 1000 4" preserveAspectRatio="none">
1433            <path d="M120 2 L880 2"/>
1434          </svg>
1435        </div>
1436
1437        <div class="trail-node">
1438          <div class="trail-disc">
1439            <span class="num">1</span>
1440            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="9" r="3.5"/><path d="M4 21c0-4 4-6 8-6s8 2 8 6"/></svg>
1441            <span class="stamp">You</span>
1442          </div>
1443          <h3>You give</h3>
1444          <p class="tn-sub">Your card. Your amount. Three taps, secure checkout.</p>
1445          <span class="tn-coord">51.5°N · 0.1°W</span>
1446        </div>
1447
1448        <div class="trail-node">
1449          <div class="trail-disc">
1450            <span class="num">2</span>
1451            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M3.5 12h17M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg>
1452            <span class="stamp">100% pass-through</span>
1453          </div>
1454          <h3>Foundation</h3>
1455          <p class="tn-sub">We hold it for hours, not weeks. Operating costs are funded separately.</p>
1456          <span class="tn-coord">37.8°N · 122.4°W</span>
1457        </div>
1458
1459        <div class="trail-node">
1460          <div class="trail-disc">
1461            <span class="num">3</span>
1462            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2C8 2 5 5 5 9c0 5.5 7 13 7 13s7-7.5 7-13c0-4-3-7-7-7z"/><circle cx="12" cy="9" r="2.5"/></svg>
1463            <span class="stamp">03.4°N 18.5°E</span>
1464          </div>
1465          <h3>Partner NGO</h3>
1466          <p class="tn-sub">Vetted, audited, on the ground. The team doing the actual work.</p>
1467          <span class="tn-coord">1.50°S · 35.14°E</span>
1468        </div>
1469
1470        <div class="trail-node">
1471          <div class="trail-disc">
1472            <span class="num">4</span>
1473            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M4 18c2-4 5-6 8-6s6 2 8 6"/><path d="M9 12V8a3 3 0 0 1 6 0v4"/><path d="M7.5 18.5l-1 2M16.5 18.5l1 2"/></svg>
1474            <span class="stamp">The wild</span>
1475          </div>
1476          <h3>The wild</h3>
1477          <p class="tn-sub">Rangers, sanctuaries, rescues, trail cams. Real animals. Real ground.</p>
1478          <span class="tn-coord">0.79°S · 113.92°E</span>
1479        </div>
1480      </div>
1481    </div>
1482  </section>
1483
1484  <!-- =================================================
1485       CINEMA BAND
1486       ================================================= -->
1487  <section class="cinema-band" aria-hidden="true">
1488    <video class="cinema-band-video" autoplay muted loop playsinline preload="metadata"
1489           poster="/assets/projects/a-free-life-for-thailands-rescued-elephants-hd.jpg"
1490           data-rate="0.75"
1491           aria-hidden="true">
1492      <source src="https://videos.pexels.com/video-files/32420379/13828839_2560_1440_25fps.mp4" type="video/mp4" />
1493    </video>
1494    <div class="cinema-band-caption">"Donations don't disappear here. They <em>arrive</em>."<span class="cs">04 · 1.50°S 35.14°E</span></div>
1495  </section>
1496
1497  <!-- =================================================
1498       COMPARE FLASH
1499       ================================================= -->
1500  <section class="compare-flash-section">
1501    <div class="compare-flash">
1502      <div class="cf-block cf-typical">
1503        <div class="cf-eyebrow">Typical charity</div>
1504        <div class="cf-num">61%</div>
1505        <div class="cf-label">reaches the field</div>
1506        <div class="cf-sub">After admin, marketing and overhead skim.</div>
1507      </div>
1508      <div class="cf-divider" aria-hidden="true"></div>
1509      <div class="cf-block cf-eco">
1510        <div class="cf-eyebrow">Ecoflix Foundation</div>
1511        <div class="cf-num">100%</div>
1512        <div class="cf-label">reaches the field</div>
1513        <div class="cf-sub">Every cent of every donation. Operations funded separately.</div>
1514      </div>
1515    </div>
1516  </section>
1517
1518  <!-- =================================================
1519       THREE OTHER WAYS
1520       ================================================= -->
1521  <section class="ways-section section-lift-soft">
1522    <div class="ways-head">
1523      <span class="eyebrow" style="display:inline-flex;margin:0 auto">Other ways</span>
1524      <h2>Not a one-time <em>kind of person?</em></h2>
1525      <p>Three deeper ways to keep the field funded month after month.</p>
1526    </div>
1527    <div class="ways-grid">
1528
1529      <a class="way-card" href="/circle-of-giving/">
1530        <span class="way-icon" aria-hidden="true">
1531          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M20 12a8 8 0 1 1-16 0 8 8 0 0 1 16 0z"/><path d="M12 4v8l5 3"/></svg>
1532        </span>
1533        <h3>Gift a membership</h3>
1534        <p>Sponsor a year of Ecoflix Channel access for someone who'd love it. Their card arrives by email, dated and signed by you.</p>
1535        <span class="way-arrow">Send a gift <span class="arrow">→</span></span>
1536      </a>
1537
1538      <a class="way-card" href="/partners/">
1539        <span class="way-icon" aria-hidden="true">
1540          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M3 13h18"/></svg>
1541        </span>
1542        <h3>Become a corporate partner</h3>
1543        <p>Co-fund a named project, sponsor a film, or wrap a season. Bespoke partnerships from $25K. Real-world impact attribution.</p>
1544        <span class="way-arrow">Talk to the team <span class="arrow">→</span></span>
1545      </a>
1546
1547      <a class="way-card" href="/circle-of-giving/">
1548        <span class="way-icon" aria-hidden="true">
1549          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M12 21s-7-4.5-7-11a5 5 0 0 1 9-3 5 5 0 0 1 9 3c0 6.5-7 11-7 11z"/></svg>
1550        </span>
1551        <h3>Join the Circle of Giving</h3>
1552        <p>For major donors funding strategic, multi-year work. Quarterly field reports. Annual gathering. Direct contact with the founders.</p>
1553        <span class="way-arrow">Apply to join <span class="arrow">→</span></span>
1554      </a>
1555
1556    </div>
1557  </section>
1558
1559  <!-- =================================================
1560       FAQ
1561       ================================================= -->
1562  <!-- =================================================
1563       CINEMA BAND
1564       ================================================= -->
1565  <section class="cinema-band" aria-hidden="true">
1566    <video class="cinema-band-video" autoplay muted loop playsinline preload="metadata"
1567           poster="/assets/projects/building-north-americas-first-whale-sanctuary-hd.jpg"
1568           data-rate="0.75"
1569           aria-hidden="true">
1570      <source src="https://videos.pexels.com/video-files/15546563/15546563-uhd_2560_1440_25fps.mp4" type="video/mp4" />
1571    </video>
1572    <div class="cinema-band-caption">"A gift big enough to be felt on the <em>reef</em>."<span class="cs">06 · 18.29°S 147.70°E</span></div>
1573  </section>
1574
1575  <section class="faq-section">
1576    <div class="bg-cartograph" aria-hidden="true">
1577      <svg viewBox="0 0 1600 900" preserveAspectRatio="xMidYMid slice">
1578        <line class="bg-lat" x1="-50" y1="220" x2="1650" y2="220"/>
1579        <line class="bg-lat" x1="-50" y1="660" x2="1650" y2="660"/>
1580        <path class="bg-trail-soft" d="M-40,500 C220,560 460,440 700,500 C940,560 1180,440 1660,500"/>
1581        <g transform="translate(140,60)"><text class="bg-coord-text" x="0" y="0">FAQ · COMMON GROUND</text></g>
1582      </svg>
1583    </div>
1584    <div class="faq-wrap">
1585      <div class="faq-head">
1586        <span class="eyebrow" style="display:inline-flex;margin:0 auto">Common questions</span>
1587        <h2>What you might be <em>wondering.</em></h2>
1588      </div>
1589
1590      <div class="faq-list">
1591        <details>
1592          <summary>Is my donation tax-deductible?</summary>
1593          <div class="faq-body">
1594            Yes for US donors — Ecoflix Foundation is a registered 501(c)(3) nonprofit. Your tax receipt is emailed to you within seconds of your donation completing. For UK Gift Aid and other regions, see <a href="/mission/">our mission page</a> or write to <a href="mailto:[email protected]">[email protected]</a>.
1595          </div>
1596        </details>
1597        <details>
1598          <summary>How do I know it actually goes to the NGO?</summary>
1599          <div class="faq-body">
1600            Every quarter we publish an impact report with project-by-project line items showing exactly which partner received what, and what they did with it. You can also visit any <a href="/projects/">project page</a> to see field updates, ranger photos and on-the-ground spend in close to real time.
1601          </div>
1602        </details>
1603        <details>
1604          <summary>Can I cancel a recurring gift?</summary>
1605          <div class="faq-body">
1606            Yes, anytime. Recurring donations are processed by Stripe — write to <a href="mailto:[email protected]">[email protected]</a> with the email you used to give and we'll pause, change or cancel the gift the same business day. Most donors are sent a Stripe customer portal link with their first receipt — one click and you can manage it yourself, no phone calls, no friction.
1607          </div>
1608        </details>
1609        <details>
1610          <summary>Can I donate to a specific project?</summary>
1611          <div class="faq-body">
1612            Absolutely. Every <a href="/projects/">project page</a> has its own dedicated donate link that routes 100% of the gift to that specific partner NGO and that specific piece of work. Give where the story moves you.
1613          </div>
1614        </details>
1615      </div>
1616    </div>
1617  </section>
1618
1619</main>
1620
1621<!-- =================================================
1622     Footer — same as homepage
1623     ================================================= -->
1624<footer class="footer" style="position:relative;overflow:hidden;isolation:isolate">
1625  <div class="bg-cartograph" aria-hidden="true">
1626    <svg viewBox="0 0 1600 900" preserveAspectRatio="xMidYMid slice">
1627      <line class="bg-lat" x1="-50" y1="120" x2="1650" y2="120"/>
1628      <line class="bg-lat" x1="-50" y1="780" x2="1650" y2="780"/>
1629      <path class="bg-trail" d="M-40,180 C260,160 540,220 800,180 C1060,140 1340,200 1660,170"/>
1630      <path class="bg-trail-soft" d="M-40,260 C220,300 460,260 700,280 C940,300 1180,260 1660,290"/>
1631      <circle class="bg-track" cx="220" cy="172" r="3"/>
1632      <circle class="bg-track" cx="300" cy="178" r="3"/>
1633      <circle class="bg-track" cx="380" cy="186" r="3"/>
1634      <circle class="bg-track" cx="460" cy="194" r="3"/>
1635      <g class="bg-marker" transform="translate(280,804)"><circle r="10"/><circle r="3.5" class="bg-marker-dot"/></g>
1636      <g class="bg-marker" transform="translate(780,800)"><circle r="12"/><circle r="4" class="bg-marker-dot"/></g>
1637      <g class="bg-marker" transform="translate(1280,804)"><circle r="10"/><circle r="3.5" class="bg-marker-dot"/></g>
1638      <g class="bg-compass" transform="translate(1490,220)">
1639        <circle class="bg-compass-ring" r="42"/>
1640        <circle class="bg-compass-ring" r="30"/>
1641        <line class="bg-compass-tick" x1="0" y1="-46" x2="0" y2="46"/>
1642        <line class="bg-compass-tick" x1="-46" y1="0" x2="46" y2="0"/>
1643        <line class="bg-compass-tick" x1="-33" y1="-33" x2="33" y2="33"/>
1644        <line class="bg-compass-tick" x1="33" y1="-33" x2="-33" y2="33"/>
1645        <polygon class="bg-compass-arrow" points="0,-42 5,0 0,42 -5,0"/>
1646        <polygon class="bg-compass-arrow" points="-42,0 0,5 42,0 0,-5"/>
1647        <text class="bg-compass-label" x="0" y="-52" text-anchor="middle">N</text>
1648        <text class="bg-compass-label" x="54" y="4" text-anchor="middle">E</text>
1649        <text class="bg-compass-label" x="0" y="64" text-anchor="middle">S</text>
1650        <text class="bg-compass-label" x="-54" y="4" text-anchor="middle">W</text>
1651      </g>
1652      <g transform="translate(140,60)"><text class="bg-coord-text" x="0" y="0">DONATE TODAY · PROTECT TOMORROW</text></g>
1653      <path class="bg-trail-soft" d="M-50,820 C260,790 540,810 860,800 C1140,790 1380,810 1700,800"/>
1654    </svg>
1655  </div>
1656  <div class="wrap" style="position:relative;z-index:2">
1657    <div class="footer-grid">
1658      <div class="footer-brand">
1659        <a href="/" class="brand" aria-label="Ecoflix home"><img src="/assets/logo.png" alt="Ecoflix" style="height:34px;width:auto;display:block" /></a>
1660        <p style="font-family:var(--display);font-style:italic;font-size:18px;color:var(--bone);margin-top:14px">Be part of the solution.</p>
1661        <p>The not-for-profit foundation behind the channel. Funding the people doing the work on the ground.</p>
1662      </div>
1663      <div>
1664        <h4 data-i18n="footer.foundation">Foundation</h4>
1665        <ul>
1666          <li><a href="/ngo-partners/">NGO Partners</a></li>
1667          <li><a href="/projects/">Conservation Projects</a></li>
1668          <li><a href="/mission/">Mission</a></li>
1669          <li><a href="/partners/">Corporate Partners</a></li>
1670          <li><a href="/team/">Team</a></li>
1671        </ul>
1672      </div>
1673      <div>
1674        <h4 data-i18n="footer.give">Give</h4>
1675        <ul>
1676          <li><a href="/donate/">Donate</a></li>
1677          <li><a href="/circle-of-giving/">Circle of Giving</a></li>
1678          <li><a href="/circle-of-giving/">Gift memberships</a></li>
1679          <li><a href="/impact-report/">Impact report</a></li>
1680        </ul>
1681      </div>
1682      <div>
1683        <h4>Watch and read</h4>
1684        <ul>
1685          <li><a href="https://watch.ecoflix.com/">Channel home</a></li>
1686          <li><a href="/news/">Ecoflix News</a></li>
1687          <li><a href="/contact/">Contact</a></li>
1688        </ul>
1689      </div>
1690    </div>
1691    <!-- Charity registration disclosure — mirrors ecoflix.com so
1692         visitors see the same legal entities, EIN/charity numbers
1693         and addresses across every page. CSS lives in site.css. -->
1694    <div class="footer-charity">
1695      <div class="fc-col">
1696        <div class="fc-eyebrow"><span class="fc-flag" aria-hidden="true">🇺🇸</span> Registered in the United States</div>
1697        <div class="fc-body">
1698          <strong>Ecoflix Foundation Inc.</strong> · 501(c)(3) not-for-profit · EIN <b>65-1212560</b><br/>
1699          24730 Long Valley Road, Hidden Hills, Los Angeles, CA 91302, USA
1700        </div>
1701      </div>
1702      <div class="fc-col">
1703        <div class="fc-eyebrow"><span class="fc-flag" aria-hidden="true">🇬🇧</span> Registered in the United Kingdom</div>
1704        <div class="fc-body">
1705          <strong>Ecoflix Foundation UK</strong> · Registered charity <b>1200441</b><br/>
1706          Aston House, Redburn Road, Newcastle Upon Tyne, NE5 1NB, United Kingdom
1707        </div>
1708      </div>
1709      <div class="fc-col fc-col-note">
1710        <div class="fc-eyebrow">100% pass-through</div>
1711        <div class="fc-body">
1712          All donations route directly to partner NGOs. Ecoflix Foundation's
1713          operating costs are covered separately by founders and corporate
1714          partners — no deductions are taken from donor gifts.
1715        </div>
1716      </div>
1717    </div>
1718
1719    <div class="footer-base">
1720      <div>© <span id="yr"></span> Ecoflix Foundation. Registered charity.</div>
1721      <div><a href="/privacy/">Privacy</a> &nbsp; <a href="/terms/">Terms</a> &nbsp; <a href="/cookies/">Cookies</a> &nbsp; <a href="/news/">Press</a> &nbsp; <a href="mailto:[email protected]">[email protected]</a></div>
1722    </div>
1723  </div>
1724</footer>
1725
1726<script>document.getElementById('yr').textContent = new Date().getFullYear();</script>
1726
1727
1728<!-- Shared site behaviours: header scroll state, mobile drawer, sdb, reveal, etc. -->
1729<script src="/assets/site.js"></script>
vendor: 1 bytes, line 1729
1729
1730<script src="/assets/i18n.js" defer></script>
1730
1731
1732<!-- Stripe.js — must be loaded from the official CDN per Stripe's TOS. -->
1733<script src="https://js.stripe.com/v3/"></script>
1733
1734
1735<!-- =================================================
1736     Donate-page local script: amount + frequency + CTA
1737     ================================================= -->
1738<script>
1739(function(){
1740  // What each amount funds, used for the live caption.
1741  // Mappings are deliberately specific — line-items, not platitudes.
1742  var IMPACT = {
1743    25:  "5 days of ranger patrols on a poaching frontline.",
1744    50:  "One rescued animal's first month of sanctuary care.",
1745    100: "10 trail-cam batteries for a snow-leopard survey.",
1746    250: "A full week of marine-debris removal from a reef."
1747  };
1748  // Fallback impact line generator for arbitrary amounts.
1749  function impactFor(amt){
1750    if (IMPACT[amt]) return IMPACT[amt];
1751    if (amt < 25)   return "Fuel and rations for a half-day ranger patrol.";
1752    if (amt < 50)   return "Vet supplies for a single rescue intake.";
1753    if (amt < 100)  return "A week of food and care for a recovering animal.";
1754    if (amt < 250)  return "Drone time over a logging frontier.";
1755    if (amt < 500)  return "Two weeks of marine-debris removal from a reef.";
1756    if (amt < 1000) return "A month of community ranger salaries.";
1757    return "A named line item in a partner NGO's quarterly field report.";
1758  }
1759
1760  var tiles    = document.querySelectorAll('.amount-tile');
1761  var custom   = document.getElementById('customAmt');
1762  var freqBtns = document.querySelectorAll('.freq-toggle button');
1763  var cta      = document.getElementById('donateCta');
1764  var ctaLabel = document.getElementById('ctaLabel');
1765  var fundsEy  = document.getElementById('fundsEyebrow');
1766  var fundsLn  = document.getElementById('fundsLine');
1767
1768  // Seed the picker from a ?amount=X query param if the visitor arrived
1769  // from an upstream CTA (e.g. the homepage calculator or any deep link).
1770  // Clamp to a sane range so we never start the picker on a weird value.
1771  function readSeedAmount(){
1772    try {
1773      var qs  = new URLSearchParams(window.location.search);
1774      var raw = parseInt(qs.get('amount') || '', 10);
1775      if (!isFinite(raw) || raw <= 0) return 50;
1776      return Math.min(Math.max(raw, 1), 100000);
1777    } catch (_) { return 50; }
1778  }
1779  function readSeedFreq(){
1780    try {
1781      var qs = new URLSearchParams(window.location.search);
1782      var v  = (qs.get('freq') || '').toLowerCase();
1783      return v === 'monthly' ? 'monthly' : 'once';
1784    } catch (_) { return 'once'; }
1785  }
1786  var state = { amount: readSeedAmount(), frequency: readSeedFreq() };
1787
1788  function selectTile(amt){
1789    state.amount = amt;
1790    tiles.forEach(function(t){
1791      var on = Number(t.dataset.amt) === amt;
1792      t.classList.toggle('is-on', on);
1793      t.setAttribute('aria-checked', on ? 'true' : 'false');
1794    });
1795    // Clear the custom field unless the custom field itself originated the change.
1796    if (document.activeElement !== custom) custom.value = '';
1797    render();
1798  }
1799
1800  function selectFreq(f){
1801    state.frequency = f;
1802    freqBtns.forEach(function(b){
1803      var on = b.dataset.freq === f;
1804      b.classList.toggle('is-on', on);
1805      b.setAttribute('aria-selected', on ? 'true' : 'false');
1806    });
1807    render();
1808  }
1809
1810  // Tiny helper: format an amount in the visitor's local currency if the
1811  // i18n layer has booted, else fall back to plain USD. Stripe still
1812  // charges in USD — these labels are display-only.
1813  function priceLabel(usd){
1814    if (window.__ECOFLIX_FORMAT__ && window.__ECOFLIX_LOCALE__
1815        && window.__ECOFLIX_LOCALE__.currency !== 'USD'){
1816      return window.__ECOFLIX_FORMAT__(usd);
1817    }
1818    return '$' + usd;
1819  }
1820
1821  function render(){
1822    var amt = state.amount > 0 ? state.amount : 50;
1823    var freq = state.frequency;
1824    var suffix = freq === 'monthly' ? '/mo' : '';
1825    var label = priceLabel(amt);
1826    fundsEy.textContent = 'What this funds at ' + label + suffix;
1827    fundsLn.textContent = impactFor(amt);
1828    ctaLabel.textContent = 'Continue to checkout · ' + label + suffix;
1829
1830    // Re-render the preset tiles in the visitor's local currency too.
1831    if (window.__ECOFLIX_LOCALE__ && window.__ECOFLIX_LOCALE__.currency !== 'USD'){
1832      tiles.forEach(function(t){
1833        var usd  = Number(t.dataset.amt) || 0;
1834        var sufEl = t.querySelector('.at-suf');
1835        var sufHtml = sufEl ? sufEl.outerHTML : '';
1836        // Rebuild — the inner $ + number + suffix span — using local money
1837        t.innerHTML = '<span class="at-curr">' + priceLabel(usd).charAt(0) + '</span>'
1838                    + priceLabel(usd).slice(1) + sufHtml;
1839      });
1840    }
1841
1842    // Expose live state to the checkout module so it can mirror amount/frequency
1843    // changes without re-querying the DOM. The window snapshot covers the very
1844    // first render, which fires before the checkout module's listener exists.
1845    window.__ECOFLIX_DONATE_STATE__ = { amount: amt, frequency: freq };
1846    window.dispatchEvent(new CustomEvent('donate:state', { detail: { amount: amt, frequency: freq } }));
1847  }
1848
1849  // Re-render when the i18n layer boots (it fires after this IIFE).
1850  window.addEventListener('ecoflix:locale-ready', function(ev){
1851    render();
1852    // Reveal the currency note if the visitor isn't on USD.
1853    var note = document.getElementById('currencyNote');
1854    if (note && ev.detail && ev.detail.currency && ev.detail.currency !== 'USD'){
1855      note.style.display = '';
1856    }
1857    // The custom field is always typed in USD; say so once amounts around it
1858    // have switched to the local currency.
1859    if (ev.detail && ev.detail.currency && ev.detail.currency !== 'USD'){
1860      var cLabel = document.getElementById('customAmtLabel');
1861      if (cLabel) cLabel.textContent = 'Custom (USD)';
1862    }
1863    paintLocaleCard(ev.detail || {});
1864  });
1865
1866  // === Contextual locale card (top-right of the picker shell) ===
1867  // Fetches the visitor's lat/lon/city/country from /api/geo and
1868  // paints the card. Falls back to the i18n country centroid if the
1869  // edge function isn't available (preview mode etc.).
1870  function fmtCoordPlainDP(d, kind){
1871    if (!isFinite(d)) return '—';
1872    var dir = kind === 'lat' ? (d >= 0 ? 'N' : 'S') : (d >= 0 ? 'E' : 'W');
1873    return Math.abs(d).toFixed(2) + '° ' + dir;
1874  }
1875  async function paintLocaleCard(loc){
1876    var $card    = document.getElementById('dpLocale');
1877    var $flag    = document.getElementById('dpLocaleFlag');
1878    var $country = document.getElementById('dpLocaleCountry');
1879    var $where   = document.getElementById('dpLocaleWhere');
1880    var $coord   = document.getElementById('dpLocaleCoord');
1881    var $ccy     = document.getElementById('dpLocaleCcy');
1882    var $toggle  = document.getElementById('dpLocaleToggle');
1883    if (!$card) return;
1884
1885    // Defaults from i18n.js (browser language / country centroid)
1886    var info = (window.__ECOFLIX_COUNTRY__ && window.__ECOFLIX_COUNTRY__()) || null;
1887    var lat = info ? info.lat : null;
1888    var lon = info ? info.lon : null;
1889    var city = '';
1890    var country = info ? info.name : (loc.country || '');
1891    var flag = info ? info.flag : '🌐';
1892    var ccy  = loc.currency || 'USD';
1893    var isDefault = (ccy === 'USD' && (loc.lang || 'en') === 'en');
1894
1895    // Try /api/geo for sharper city + lat/lon
1896    try {
1897      var resp = await fetch('/api/geo', { credentials: 'same-origin' });
1898      if (resp.ok){
1899        var g = await resp.json();
1900        if (g && g.ok){
1901          lat = g.lat;
1902          lon = g.lon;
1903          if (g.city) city = g.city;
1904          if (g.country) country = country || g.country;
1905        }
1906      }
1907    } catch (_){}
1908
1909    $flag.textContent    = flag;
1910    $country.textContent = country || 'Detected';
1911    $where.textContent   = city ? (city + (country ? ', ' + country : '')) : (country || '');
1912    $coord.textContent   = (lat != null && lon != null)
1913      ? (fmtCoordPlainDP(lat, 'lat') + '  ·  ' + fmtCoordPlainDP(lon, 'lon'))
1914      : '— · —';
1915    $ccy.textContent     = ccy;
1916    $card.classList.toggle('is-default', isDefault);
1917  }
1918  // Try to paint immediately (in case i18n.js already booted)
1919  if (window.__ECOFLIX_LOCALE__) paintLocaleCard(window.__ECOFLIX_LOCALE__);
1920
1921  // Wire the "Use English · USD" pill on the locale card
1922  (function(){
1923    var $toggle = document.getElementById('dpLocaleToggle');
1924    if (!$toggle) return;
1925    $toggle.addEventListener('click', function(ev){
1926      ev.preventDefault();
1927      try {
1928        localStorage.setItem('ecoflix:locale', JSON.stringify({ lang:'en', currency:'USD', userChose:true }));
1929      } catch (_){}
1930      window.location.reload();
1931    });
1932  })();
1933
1934  tiles.forEach(function(t){
1935    t.addEventListener('click', function(){ selectTile(Number(t.dataset.amt)); });
1936  });
1937  freqBtns.forEach(function(b){
1938    b.addEventListener('click', function(){ selectFreq(b.dataset.freq); });
1939  });
1940  custom.addEventListener('input', function(){
1941    var v = parseInt(custom.value, 10);
1942    if (isNaN(v) || v <= 0){ state.amount = 50; render(); return; }
1943    state.amount = v;
1944    // Clear preset selection when the custom field has a value.
1945    tiles.forEach(function(t){ t.classList.remove('is-on'); t.setAttribute('aria-checked','false'); });
1946    render();
1947  });
1948
1949  // Initial render — reflect the seeded amount (?amount=X) or default $50.
1950  // If the seed matches a preset tile, light it up. Otherwise put it in
1951  // the custom field so the donor sees their amount carried through.
1952  (function seedPickerUI(){
1953    var seed = state.amount;
1954    var seedFreq = state.frequency;
1955    // Sync the frequency toggle UI
1956    freqBtns.forEach(function(b){
1957      var on = b.dataset.freq === seedFreq;
1958      b.classList.toggle('is-on', on);
1959      b.setAttribute('aria-selected', on ? 'true' : 'false');
1960    });
1961    // Try to match a preset
1962    var matched = false;
1963    tiles.forEach(function(t){
1964      var on = Number(t.dataset.amt) === seed;
1965      t.classList.toggle('is-on', on);
1966      t.setAttribute('aria-checked', on ? 'true' : 'false');
1967      if (on) matched = true;
1968    });
1969    // If no preset matched, push it into the custom input
1970    if (!matched && seed !== 50){
1971      custom.value = String(seed);
1972    }
1973  })();
1974  render();
1975})();
1976</script>
1976
1977
1978<!-- =================================================
1979     Stripe inline checkout
1980     - Fetches /api/donate/config for the publishable key
1981     - Activates on "Continue to checkout" click
1982     - Mounts Express Checkout (Apple Pay / Google Pay / Link)
1983       and Payment Element
1984     - Submits via stripe.confirmPayment with return_url=/thank-you/
1985     ================================================= -->
1986<script>
1987(function(){
1988  if (!window.Stripe){
1989    console.warn('[checkout] Stripe.js failed to load. Inline checkout disabled.');
1990    return;
1991  }
1992
1993  // ---------- DOM refs ----------
1994  var $cta        = document.getElementById('donateCta');
1995  var $section    = document.getElementById('checkout');
1996  var $shell      = $section ? $section.querySelector('.checkout-shell') : null;
1997  var $sumAmt     = document.getElementById('chSumAmt');
1998  var $summary    = document.getElementById('chSummary');
1999  var $email      = document.getElementById('donorEmail');
2000  var $name       = document.getElementById('donorName');
2001  var $dedication = document.getElementById('donorDedication');
2002  var $submit     = document.getElementById('chSubmit');
2003  var $submitLab  = document.getElementById('chSubmitLabel');
2004  var $error      = document.getElementById('chError');
2005  var $togMonthly = document.getElementById('togMonthly');
2006  var $togFee     = document.getElementById('togCoverFee');
2007  var $togMthMeta = document.getElementById('togMonthlyMeta');
2008  var $togFeeMeta = document.getElementById('togCoverFeeMeta');
2009  var $expressMnt = document.getElementById('expressCheckoutElement');
2010  var $payMnt     = document.getElementById('paymentElement');
2011  var $divider    = document.getElementById('chDivider');
2012  var $quickZone  = document.getElementById('quickDonateZone');
2013
2014  if (!$cta || !$section || !$shell || !$submit){
2015    console.warn('[checkout] missing required DOM nodes.');
2016    return;
2017  }
2018
2019  // ---------- State ----------
2020  var picker  = { amount: 50, frequency: 'once' }; // mirror of amount picker
2021  var ui      = { monthly: false, coverFee: false }; // local toggles
2022  var stripe  = null;
2023  var elements = null;
2024  var paymentElement = null;
2025  var expressElement = null;
2026  var clientSecret = null;
2027  var lastIntentKey = ''; // dedupe re-creation of the same intent
2028  var creatingIntent = false;
2029  var activated = false; // checkout has been opened
2030  var publishableKey = '';
2031
2032  // ---------- Helpers ----------
2033  function fmt(n){ return '$' + (Math.round(n * 100) / 100).toFixed(n % 1 === 0 ? 0 : 2); }
2034
2035  // Local-currency continuity: the amount picker shows nice-rounded local
2036  // labels (e.g. the $50 tile reads £40), so checkout must speak the same
2037  // language. pairLabel() renders "£40 ($50 USD)" for non-USD visitors and
2038  // plain "$50" for everyone else. `nice` uses the same rounded label as the
2039  // tiles (only correct for the tile amounts themselves); exact amounts
2040  // (fees, fee-inclusive totals) convert at the live rate instead.
2041  function localCcy(){
2042    return window.__ECOFLIX_LOCALE__ && window.__ECOFLIX_LOCALE__.currency !== 'USD'
2043      && window.__ECOFLIX_FORMAT__ && window.__ECOFLIX_CONVERT__;
2044  }
2045  function exactLocal(usd){
2046    var v = Math.round(window.__ECOFLIX_CONVERT__(usd) * 100) / 100;
2047    var sym = String(window.__ECOFLIX_FORMAT__(0) || '').replace(/[\d.,\s ]/g, '');
2048    if (!sym) sym = (window.__ECOFLIX_LOCALE__.currency || '') + ' ';
2049    return sym + v.toFixed(v % 1 === 0 ? 0 : 2);
2050  }
2051  function pairLabel(usd, nice){
2052    if (!localCcy()) return fmt(usd);
2053    var local = nice ? window.__ECOFLIX_FORMAT__(usd) : exactLocal(usd);
2054    return local + ' (' + fmt(usd) + ' USD)';
2055  }
2056
2057  function feeFor(amount){
2058    // Standard "cover the fee" calc: 2.9% + $0.30 of the gross, rounded up.
2059    // Math.ceil(amount * 0.029 * 100 + 30) / 100 — in cents-then-dollars.
2060    var cents = Math.ceil(amount * 0.029 * 100 + 30);
2061    return cents / 100;
2062  }
2063
2064  // Final amount sent to Stripe = picker amount + fee if "cover fee" is on.
2065  function totalAmount(){
2066    var base = picker.amount > 0 ? picker.amount : 50;
2067    if (ui.coverFee) base = base + feeFor(base);
2068    return Math.round(base * 100) / 100;
2069  }
2070
2071  // Effective frequency = monthly toggle in checkout OR picker monthly choice.
2072  function effectiveFrequency(){
2073    if (ui.monthly) return 'monthly';
2074    return picker.frequency === 'monthly' ? 'monthly' : 'once';
2075  }
2076
2077  function syncSummary(){
2078    var amt = totalAmount();
2079    var freq = effectiveFrequency();
2080    // The picked amount is a preset tile (nice local label); once the fee
2081    // top-up is added it's no longer a tile amount, so convert exactly.
2082    var niceAmt = !ui.coverFee;
2083    if ($sumAmt) $sumAmt.textContent = pairLabel(amt, niceAmt);
2084    if ($summary){
2085      // Rebuild the text but keep the highlighted amount span intact.
2086      $summary.innerHTML = 'You\'re giving <span class="ch-sum-amt" id="chSumAmt">' +
2087        pairLabel(amt, niceAmt) + '</span> ' + (freq === 'monthly' ? '/ month' : '· one-time');
2088    }
2089    var baseAmt = picker.amount > 0 ? picker.amount : 50;
2090    if ($togMthMeta){
2091      $togMthMeta.textContent = pairLabel(baseAmt, true) + ' / month, recurring. Cancel anytime.';
2092    }
2093    if ($togFeeMeta){
2094      $togFeeMeta.textContent = '+' + pairLabel(feeFor(baseAmt), false) + ' — keeps 100% of your gift reaching the field.';
2095    }
2096    if ($submitLab){
2097      var word = freq === 'monthly'
2098        ? 'DONATE ' + pairLabel(amt, niceAmt) + '/MO'
2099        : 'DONATE ' + pairLabel(amt, niceAmt);
2100      $submitLab.textContent = word;
2101    }
2102  }
2103
2104  function showError(msg){
2105    if (!$error) return;
2106    if (msg){
2107      $error.textContent = msg;
2108      $error.classList.add('is-visible');
2109    } else {
2110      $error.textContent = '';
2111      $error.classList.remove('is-visible');
2112    }
2113  }
2114
2115  function setLoading(on){
2116    if (on){
2117      $submit.classList.add('is-loading');
2118      $submit.disabled = true;
2119    } else {
2120      $submit.classList.remove('is-loading');
2121      $submit.disabled = !canSubmit();
2122    }
2123  }
2124
2125  function canSubmit(){
2126    var emailOK = $email && /.+@.+\..+/.test($email.value || '');
2127    return Boolean(clientSecret) && emailOK && paymentElement;
2128  }
2129
2130  function updateSubmitEnabled(){
2131    if (fallbackMode){ $submit.disabled = false; return; }
2132    if (!$submit.classList.contains('is-loading')){
2133      $submit.disabled = !canSubmit();
2134    }
2135  }
2136
2137  // ---------- Stripe bootstrap ----------
2138  // If Stripe isn't reachable (missing key, config endpoint down), donors
2139  // must never see a developer error. Fall back to the Givebutter page —
2140  // the previous live donate flow, still active — so giving keeps working.
2141  var FALLBACK_DONATE_URL = 'https://givebutter.com/ecoflix';
2142  var fallbackMode = false;
2143  function showFallback(){
2144    fallbackMode = true;
2145    if ($error){
2146      $error.innerHTML = 'Card payments on this page are being upgraded right now. ' +
2147        'You can still give in under a minute on our secure donation page — ' +
2148        '<a href="' + FALLBACK_DONATE_URL + '" target="_blank" rel="noopener" ' +
2149        'style="color:inherit;font-weight:700;text-decoration:underline">donate via Givebutter</a>. ' +
2150        '100% still reaches the partner NGO.';
2151      $error.classList.add('is-visible');
2152    }
2153    if ($submitLab) $submitLab.textContent = 'DONATE ON OUR SECURE PAGE';
2154    if ($submit){
2155      $submit.disabled = false;
2156      $submit.classList.remove('is-loading');
2157      $submit.addEventListener('click', function(ev){
2158        ev.preventDefault();
2159        window.open(FALLBACK_DONATE_URL, '_blank', 'noopener');
2160      });
2161    }
2162  }
2163
2164  async function loadConfig(){
2165    try {
2166      var resp = await fetch('/api/donate/config', { credentials: 'same-origin' });
2167      if (!resp.ok) throw new Error('Config endpoint returned ' + resp.status);
2168      var data = await resp.json();
2169      if (!data.publishableKey){
2170        throw new Error('No publishable key configured. Set STRIPE_PUBLISHABLE_KEY in Vercel.');
2171      }
2172      publishableKey = data.publishableKey;
2173      stripe = Stripe(publishableKey);
2174      return true;
2175    } catch (err){
2176      console.warn('[checkout] config load failed:', err);
2177      showFallback();
2178      return false;
2179    }
2180  }
2181
2182  // Appearance hoisted to module scope so both mountExpress + mountPayment
2183  // share the same Stripe Elements visual treatment.
2184  var appearance = {
2185    theme: 'night',
2186    labels: 'above',
2187    variables: {
2188      colorPrimary: '#A8F341',
2189      colorBackground: '#0a1226',
2190      colorText: '#f5f3ee',
2191      colorTextSecondary: '#b9bcc7',
2192      colorTextPlaceholder: '#6f7689',
2193      colorDanger: '#e26a5b',
2194      colorIcon: '#b9bcc7',
2195      colorIconHover: '#A8F341',
2196      fontFamily: '"Manrope", system-ui, sans-serif',
2197      fontSizeBase: '15px',
2198      spacingUnit: '4px',
2199      borderRadius: '10px',
2200    },
2201    rules: {
2202      '.Input': {
2203        backgroundColor: 'rgba(255,255,255,0.03)',
2204        border: '1px solid rgba(255,255,255,0.08)',
2205        boxShadow: 'none',
2206      },
2207      '.Input:focus': {
2208        border: '1px solid #A8F341',
2209        boxShadow: '0 0 0 3px rgba(168,243,65,0.18)',
2210      },
2211      '.Label': {
2212        fontFamily: '"Manrope", system-ui, sans-serif',
2213        fontWeight: '700',
2214        fontSize: '11px',
2215        letterSpacing: '0.18em',
2216        textTransform: 'uppercase',
2217        color: '#6f7689',
2218      },
2219      '.Tab': {
2220        backgroundColor: 'rgba(255,255,255,0.03)',
2221        border: '1px solid rgba(255,255,255,0.08)',
2222      },
2223      '.Tab--selected': {
2224        borderColor: '#A8F341',
2225        backgroundColor: 'rgba(168,243,65,0.08)',
2226      },
2227    },
2228  };
2229
2230  // Ensure the Stripe Elements instance exists and has a clientSecret.
2231  // Used by both mountExpress + mountPayment.
2232  async function ensureElements(){
2233    var ok = await ensureIntent();
2234    if (!ok) return false;
2235    if (!elements){
2236      elements = stripe.elements({ clientSecret: clientSecret, appearance: appearance });
2237    }
2238    return true;
2239  }
2240
2241  // Create + mount the Express Checkout Element on the current Elements
2242  // group. Split out from mountExpress so the element can be rebuilt when
2243  // the intent (and therefore the Elements group) changes.
2244  function buildExpress(){
2245    expressElement = elements.create('expressCheckout', {
2246      buttonHeight: 48,
2247    });
2248    expressElement.mount('#expressCheckoutElement');
2249    expressElement.on('ready', function(e){
2250      var hasMethods = e.availablePaymentMethods
2251        && Object.keys(e.availablePaymentMethods).some(function(k){
2252            return !!e.availablePaymentMethods[k];
2253          });
2254      if (hasMethods){
2255        if ($quickZone){
2256          $quickZone.classList.add('is-on');
2257          $quickZone.setAttribute('aria-hidden', 'false');
2258        }
2259      } else {
2260        // No wallets on this device — leave the Quick Donate zone hidden
2261        // and let the donor use the Continue-to-checkout flow.
2262        if ($quickZone) $quickZone.classList.remove('is-on');
2263        if ($divider) $divider.style.display = 'none';
2264      }
2265    });
2266    expressElement.on('confirm', onExpressConfirm);
2267  }
2268
2269  // Mount the Express Checkout Element (Apple Pay / Google Pay / Link).
2270  // Booted eagerly on page load so the Quick Donate buttons appear
2271  // by the picker before the donor scrolls anywhere near the form.
2272  async function mountExpress(){
2273    if (!stripe){
2274      var booted = await loadConfig();
2275      if (!booted) return false;
2276    }
2277    var ok = await ensureElements();
2278    if (!ok) return false;
2279    if (expressElement) return true; // already mounted
2280
2281    try {
2282      buildExpress();
2283    } catch (err){
2284      console.warn('[checkout] expressCheckout unavailable:', err);
2285      if ($quickZone) $quickZone.classList.remove('is-on');
2286      if ($divider) $divider.style.display = 'none';
2287      return false;
2288    }
2289    return true;
2290  }
2291
2292  // Create + mount the Payment Element on the current Elements group.
2293  // Split out from mountPayment so the element can be rebuilt when the
2294  // intent (and therefore the Elements group) changes.
2295  function buildPayment(){
2296    paymentElement = elements.create('payment', {
2297      layout: { type: 'tabs', defaultCollapsed: false },
2298      defaultValues: {
2299        billingDetails: {
2300          email: $email.value || undefined,
2301          name:  $name.value  || undefined,
2302        },
2303      },
2304    });
2305    paymentElement.mount('#paymentElement');
2306    paymentElement.on('change', function(){ updateSubmitEnabled(); });
2307    paymentElement.on('ready', function(){ updateSubmitEnabled(); });
2308  }
2309
2310  // Mount the Payment Element (card / bank / wallets). Lazy — only fires
2311  // once the donor has clicked Continue to checkout, since the iframe
2312  // would not size correctly inside the still-hidden shell.
2313  async function mountPayment(){
2314    if (!stripe){
2315      var booted = await loadConfig();
2316      if (!booted) return false;
2317    }
2318    var ok = await ensureElements();
2319    if (!ok) return false;
2320    if (paymentElement) return true; // already mounted
2321
2322    buildPayment();
2323    return true;
2324  }
2325
2326  // Late-mount whatever could not mount earlier because the intent did not
2327  // exist yet (e.g. monthly was chosen before the donor's email existed).
2328  async function mountPending(){
2329    if (!expressElement) await mountExpress();
2330    if (activated && !paymentElement) await mountPayment();
2331  }
2332
2333  // Computes the key that identifies "the intent we currently have". If amount,
2334  // frequency or coverFee changes, we need a fresh PaymentIntent/Subscription.
2335  // The email only matters for monthly (the subscription's Stripe Customer is
2336  // created with it); one-time receipts ride on confirmParams.receipt_email,
2337  // so a changed email must not churn the one-time intent.
2338  function currentIntentKey(){
2339    var freq = effectiveFrequency();
2340    var parts = [freq, Math.round(totalAmount() * 100)];
2341    if (freq === 'monthly') parts.push(($email && $email.value) || '');
2342    return parts.join('|');
2343  }
2344
2345  // Lazy-creates the intent, or rebuilds Elements if amount/frequency changed.
2346  async function ensureIntent(){
2347    if (!stripe) return false;
2348    var key = currentIntentKey();
2349    if (key === lastIntentKey && clientSecret) return true;
2350    if (creatingIntent) return false;
2351
2352    // A subscription cannot be created without the donor's email (Stripe
2353    // needs a Customer), so don't send a request the server will reject.
2354    // The email blur handler retries once the address exists.
2355    if (effectiveFrequency() === 'monthly'
2356        && !/.+@.+\..+/.test(($email && $email.value) || '')){
2357      if (activated){
2358        showError('Add your email above to set up monthly giving.');
2359      }
2360      return false;
2361    }
2362
2363    creatingIntent = true;
2364    setLoading(true);
2365    showError('');
2366
2367    try {
2368      var freq = effectiveFrequency();
2369      var endpoint = freq === 'monthly'
2370        ? '/api/donate/create-subscription'
2371        : '/api/donate/create-payment-intent';
2372
2373      var payload = {
2374        amount: totalAmount(),
2375        currency: 'usd',
2376        email: ($email && $email.value) || '',
2377        name:  ($name  && $name.value)  || '',
2378        dedication: ($dedication && $dedication.value) || '',
2379        coverFee: ui.coverFee,
2380        ngo: (window.__ECOFLIX_NGO__ && window.__ECOFLIX_NGO__.id) || '',
2381        ngo_name: (window.__ECOFLIX_NGO__ && window.__ECOFLIX_NGO__.name) || '',
2382        project: (window.__ECOFLIX_PROJECT__ && window.__ECOFLIX_PROJECT__.id) || '',
2383        project_name: (window.__ECOFLIX_PROJECT__ && window.__ECOFLIX_PROJECT__.title) || '',
2384      };
2385      if (freq === 'monthly') payload.interval = 'month';
2386
2387      var resp = await fetch(endpoint, {
2388        method: 'POST',
2389        headers: { 'content-type': 'application/json' },
2390        credentials: 'same-origin',
2391        body: JSON.stringify(payload),
2392      });
2393      var data = await resp.json();
2394      if (!resp.ok || !data.clientSecret){
2395        throw new Error(data.error || 'Could not start checkout.');
2396      }
2397      clientSecret = data.clientSecret;
2398      lastIntentKey = key;
2399
2400      // A Stripe Elements group is permanently bound to the clientSecret it
2401      // was created with — elements.update() cannot change it. When the
2402      // intent changes we must rebuild the group and remount whatever was
2403      // already on the page, otherwise confirmPayment() runs against the
2404      // stale intent and the donation is rejected.
2405      if (elements){
2406        var hadExpress = !!expressElement;
2407        var hadPayment = !!paymentElement;
2408        if (expressElement){ try { expressElement.unmount(); } catch (_){} expressElement = null; }
2409        if (paymentElement){ try { paymentElement.unmount(); } catch (_){} paymentElement = null; }
2410        elements = stripe.elements({ clientSecret: clientSecret, appearance: appearance });
2411        if (hadExpress){
2412          try { buildExpress(); }
2413          catch (err){
2414            console.warn('[checkout] expressCheckout unavailable:', err);
2415            if ($quickZone) $quickZone.classList.remove('is-on');
2416          }
2417        }
2418        if (hadPayment) buildPayment();
2419      }
2420      return true;
2421    } catch (err){
2422      console.error('[checkout] ensureIntent failed:', err);
2423      showError(err.message || 'Could not start checkout. Please try again.');
2424      return false;
2425    } finally {
2426      creatingIntent = false;
2427      setLoading(false);
2428    }
2429  }
2430
2431  // ---------- Activation / open ----------
2432  function focusTrap(e){
2433    if (!activated || e.key !== 'Tab') return;
2434    var focusables = $shell.querySelectorAll(
2435      'a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])'
2436    );
2437    if (!focusables.length) return;
2438    var first = focusables[0];
2439    var last  = focusables[focusables.length - 1];
2440    if (e.shiftKey && document.activeElement === first){
2441      e.preventDefault(); last.focus();
2442    } else if (!e.shiftKey && document.activeElement === last){
2443      e.preventDefault(); first.focus();
2444    }
2445  }
2446
2447  async function activate(opts){
2448    opts = opts || {};
2449    var target = opts.target || null; // optional selector or element to scroll to
2450    var skipFocus = !!opts.skipFocus;
2451
2452    function scrollAfter(){
2453      var el = null;
2454      if (target){
2455        el = typeof target === 'string' ? document.querySelector(target) : target;
2456      }
2457      var dest = el || $section;
2458      dest.scrollIntoView({ behavior: 'smooth', block: 'start' });
2459      if (!skipFocus && !target && $email) $email.focus();
2460    }
2461
2462    if (activated){
2463      setTimeout(scrollAfter, 30);
2464      return;
2465    }
2466    activated = true;
2467    $section.classList.add('is-open');
2468    $section.setAttribute('aria-hidden', 'false');
2469    $cta.setAttribute('aria-expanded', 'true');
2470
2471    // Pull current picker state into our local toggles for the first time.
2472    ui.monthly = picker.frequency === 'monthly';
2473    if (ui.monthly){
2474      $togMonthly.classList.add('is-on');
2475      $togMonthly.setAttribute('aria-checked', 'true');
2476    }
2477    syncSummary();
2478
2479    // Smooth scroll + (optional) focus.
2480    setTimeout(scrollAfter, 60);
2481
2482    document.addEventListener('keydown', focusTrap);
2483
2484    // Boot Stripe if not yet booted (the eager-boot below usually has),
2485    // then mount the Payment Element (lazy — needs a visible container).
2486    if (!stripe){
2487      var ok = await loadConfig();
2488      if (!ok) return;
2489    }
2490    if (!paymentElement){
2491      await mountPayment();
2492    } else {
2493      await ensureIntent();
2494    }
2495  }
2496
2497  // ---------- Express checkout confirm ----------
2498  async function onExpressConfirm(event){
2499    if (!stripe || !elements) return;
2500    // Monthly needs the donor's email before a subscription can exist. If a
2501    // wallet donor taps Quick Donate in monthly mode, open the form so they
2502    // can add it.
2503    if (effectiveFrequency() === 'monthly'
2504        && !/.+@.+\..+/.test(($email && $email.value) || '')){
2505      activate();
2506      return;
2507    }
2508    var ok = await ensureIntent();
2509    if (!ok) return;
2510    setLoading(true);
2511    var ret = await stripe.confirmPayment({
2512      elements: elements,
2513      confirmParams: {
2514        return_url: buildReturnUrl(),
2515        receipt_email: ($email && $email.value) || undefined,
2516      },
2517    });
2518    if (ret.error){
2519      showError(ret.error.message || 'Payment did not complete.');
2520      setLoading(false);
2521    }
2522    // On success, Stripe redirects automatically.
2523  }
2524
2525  function buildReturnUrl(){
2526    var amt = totalAmount();
2527    var freq = effectiveFrequency();
2528    var donor = ($name && $name.value) || ($email && $email.value) || '';
2529    var qs = new URLSearchParams({
2530      amount: String(amt),
2531      currency: 'USD',
2532      donor: donor,
2533      frequency: freq,
2534    });
2535    return window.location.origin + '/thank-you/?' + qs.toString();
2536  }
2537
2538  // ---------- Standard card submit ----------
2539  async function onSubmit(){
2540    if (fallbackMode) return; // fallback click handler owns the button now
2541    if (!stripe || !elements || !paymentElement) return;
2542    showError('');
2543
2544    if (!$email.value || !/.+@.+\..+/.test($email.value)){
2545      $email.classList.add('is-invalid');
2546      $email.focus();
2547      showError('Please enter a valid email so we can send your receipt.');
2548      return;
2549    }
2550    $email.classList.remove('is-invalid');
2551
2552    // Make sure the intent reflects the current picker / toggles / email.
2553    var ok = await ensureIntent();
2554    if (!ok) return;
2555
2556    setLoading(true);
2557    var ret = await stripe.confirmPayment({
2558      elements: elements,
2559      confirmParams: {
2560        return_url: buildReturnUrl(),
2561        receipt_email: $email.value,
2562        payment_method_data: {
2563          billing_details: {
2564            email: $email.value || undefined,
2565            name:  $name.value  || undefined,
2566          },
2567        },
2568      },
2569    });
2570    if (ret.error){
2571      showError(ret.error.message || 'Payment did not complete.');
2572      setLoading(false);
2573    }
2574  }
2575
2576  // ---------- Wire up listeners ----------
2577  $cta.addEventListener('click', activate);
2578
2579  // "Read full security details" jump link in the compact trust strip
2580  // up next to the amount picker. Opens the checkout shell (so the full
2581  // trust panel is rendered), scrolls straight to it, flashes a leaf
2582  // highlight, and skips auto-focus on the email field so the donor can
2583  // read without the keyboard popping up on mobile.
2584  var $trustLink  = document.getElementById('trustStripLink');
2585  var $trustPanel = document.getElementById('trust-panel');
2586  if ($trustLink && $trustPanel){
2587    $trustLink.addEventListener('click', function(ev){
2588      ev.preventDefault();
2589      activate({ target: '#trust-panel', skipFocus: true });
2590      // Flash the panel so the eye lands on it after the scroll.
2591      setTimeout(function(){
2592        $trustPanel.classList.add('is-flash');
2593        // Move focus there for keyboard / screen-reader users.
2594        try { $trustPanel.focus({ preventScroll: true }); } catch(_){}
2595        setTimeout(function(){
2596          $trustPanel.classList.remove('is-flash');
2597        }, 1800);
2598      }, 480);
2599    });
2600  }
2601
2602  // Re-create the intent when the donor email changes (the intent key
2603  // includes the email for monthly), then late-mount the card form if it
2604  // was blocked waiting on the email.
2605  if ($email){
2606    $email.addEventListener('blur', function(){
2607      ensureIntent().then(function(ok){ if (ok) mountPending(); });
2608    });
2609    $email.addEventListener('input', updateSubmitEnabled);
2610  }
2611
2612  // Toggle: monthly
2613  function setMonthly(on){
2614    ui.monthly = !!on;
2615    $togMonthly.classList.toggle('is-on', ui.monthly);
2616    $togMonthly.setAttribute('aria-checked', ui.monthly ? 'true' : 'false');
2617    syncSummary();
2618    if (activated){
2619      ensureIntent().then(function(ok){ if (ok) mountPending(); });
2620    }
2621  }
2622  function setCoverFee(on){
2623    ui.coverFee = !!on;
2624    $togFee.classList.toggle('is-on', ui.coverFee);
2625    $togFee.setAttribute('aria-checked', ui.coverFee ? 'true' : 'false');
2626    syncSummary();
2627    if (activated){
2628      ensureIntent().then(function(ok){ if (ok) mountPending(); });
2629    }
2630  }
2631  $togMonthly.addEventListener('click', function(){ setMonthly(!ui.monthly); });
2632  $togFee    .addEventListener('click', function(){ setCoverFee(!ui.coverFee); });
2633  $togMonthly.addEventListener('keydown', function(e){
2634    if (e.key === ' ' || e.key === 'Enter'){ e.preventDefault(); setMonthly(!ui.monthly); }
2635  });
2636  $togFee.addEventListener('keydown', function(e){
2637    if (e.key === ' ' || e.key === 'Enter'){ e.preventDefault(); setCoverFee(!ui.coverFee); }
2638  });
2639
2640  $submit.addEventListener('click', onSubmit);
2641
2642  // ---------- Listen for upstream picker changes ----------
2643  window.addEventListener('donate:state', function(ev){
2644    if (!ev.detail) return;
2645    picker.amount = Number(ev.detail.amount) || 50;
2646    picker.frequency = ev.detail.frequency || 'once';
2647    syncSummary();
2648    // Refresh the intent whenever Stripe is booted — covers both the
2649    // pre-activate Quick Donate path (express mounted) and the full
2650    // post-activate form path.
2651    if (stripe){
2652      ensureIntent().then(function(ok){ if (ok) mountPending(); });
2653    }
2654  });
2655
2656  // ---------- Initial sync ----------
2657  // The picker's first render() fires donate:state before this module has
2658  // registered its listener, so seed from the state the picker exposes —
2659  // otherwise ?amount= / ?freq=monthly deep links are silently ignored.
2660  if (window.__ECOFLIX_DONATE_STATE__){
2661    picker.amount = Number(window.__ECOFLIX_DONATE_STATE__.amount) || 50;
2662    picker.frequency = window.__ECOFLIX_DONATE_STATE__.frequency || 'once';
2663  }
2664  syncSummary();
2665
2666  // ---------- Eager boot: surface the Quick Donate lane ----------
2667  // Wait until the page is idle, then boot Stripe and mount the
2668  // Express Checkout Element by the picker. If at least one wallet
2669  // (Apple Pay / Google Pay / Link) is available, the Quick Donate
2670  // zone reveals itself with a soft fade-in. If none is available,
2671  // the zone stays hidden and the donor uses Continue-to-checkout.
2672  function bootQuickDonate(){
2673    if (!$quickZone) return;
2674    mountExpress().catch(function(err){
2675      console.warn('[checkout] eager boot failed:', err);
2676    });
2677  }
2678  if ('requestIdleCallback' in window){
2679    requestIdleCallback(bootQuickDonate, { timeout: 1500 });
2680  } else {
2681    setTimeout(bootQuickDonate, 800);
2682  }
2683})();
2684</script>
2684
2685
2686<!-- Shared helpers: replace the amount tiles with a partner's donation tiers
2687     ("what your gift buys"), and render a project's finite goal as progress. -->
2688<script>
2689(function(){
2690  function esc(s){return String(s).replace(/[&<>"]/g,function(c){return{'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];});}
2691  // Replace the preset amount tiles in place (keeps their click handlers).
2692  window.__ecoApplyTiers=function(tiers){
2693    if(!tiers||!tiers.length) return;
2694    var tiles=[].slice.call(document.querySelectorAll('.amount-tile'));
2695    if(!tiles.length) return;
2696    var use=tiers.slice(0,tiles.length);
2697    tiles.forEach(function(t,i){
2698      var tier=use[i];
2699      if(!tier){ t.style.display='none'; t.setAttribute('aria-hidden','true'); return; }
2700      t.style.display=''; t.removeAttribute('aria-hidden');
2701      t.dataset.amt=String(tier.amount);
2702      t.innerHTML='<span class="at-curr">$</span>'+tier.amount+'<span class="at-suf">'+esc(tier.label||'')+'</span>';
2703      t.classList.toggle('is-on', i===0);
2704      t.setAttribute('aria-checked', i===0?'true':'false');
2705    });
2706    if(tiles[0]) tiles[0].click();   // re-selects via the tile's own handler → updates amount/state
2707  };
2708  // Render a finite project goal as a progress strip below the attribution banner.
2709  window.__ecoProjectGoal=function(raised,target,purpose){
2710    target=Number(target)||0; raised=Number(raised)||0;
2711    if(target<=0) return;
2712    var banner=document.getElementById('projectDirect'); if(!banner||document.getElementById('projectGoal')) return;
2713    var pct=Math.max(0,Math.min(100,Math.round(raised/target*100)));
2714    var g=document.createElement('div'); g.id='projectGoal';
2715    g.setAttribute('style','max-width:760px;margin:0 auto 18px;padding:16px 18px;border-radius:14px;background:rgba(2,7,24,.5);border:1px solid rgba(255,255,255,.08)');
2716    g.innerHTML='<div style="display:flex;justify-content:space-between;align-items:baseline;font-size:13px;color:var(--mist);margin-bottom:8px">'
2717      +'<span><b style="color:var(--bone);font-size:15px">$'+Math.round(raised).toLocaleString()+'</b> raised</span>'
2718      +'<span>'+pct+'% of $'+Math.round(target).toLocaleString()+' goal</span></div>'
2719      +'<div style="height:8px;border-radius:5px;background:rgba(255,255,255,.08);overflow:hidden"><div style="height:100%;width:'+pct+'%;background:linear-gradient(90deg,var(--leaf),#D3FA9E)"></div></div>'
2720      +(purpose?'<div style="font-size:13px;color:var(--mist);line-height:1.5;margin-top:10px">'+esc(purpose)+'</div>':'');
2721    banner.parentNode.insertBefore(g, banner.nextSibling);
2722  };
2723})();
2724</script>
2724
2725
2726<!-- Project-directed donations: ?project=<slug> gives the gift a specific,
2727     named destination (the project) and, where it has one, the delivering NGO.
2728     Both are sent to checkout as metadata. -->
2729<script>
2730(function(){
2731  function esc(s){return String(s).replace(/[&<>"]/g,function(c){return{'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];});}
2732  var slug=new URLSearchParams(location.search).get('project');
2733  if(!slug) return;
2734  fetch('/assets/projects.json').then(function(r){return r.json();}).then(function(d){
2735    var p=(d.projects||[]).find(function(x){return x.id===slug;});
2736    if(!p) return;
2737    var title=p.title||'this project';
2738    var ngo=p.ngo||'';
2739    window.__ECOFLIX_PROJECT__={id:p.id,title:title};
2740    if(p.partner_slug){ window.__ECOFLIX_NGO__={id:p.partner_slug,name:ngo}; }
2741    var h1=document.querySelector('h1');
2742    if(h1) h1.innerHTML='Your gift goes <em>directly to '+esc(title)+'.</em>';
2743    var lede=document.querySelector('.lede');
2744    if(lede) lede.textContent='100% of your donation backs '+title+(ngo?(', delivered by '+ngo):'')+'. Ecoflix takes nothing, and your gift is never pooled into a general fund.';
2745    var shell=document.querySelector('.donate-shell');
2746    if(shell && !document.getElementById('projectDirect')){
2747      var imgOk=/\.(jpg|jpeg|png|webp|gif|avif)(\?|#|$)/i.test(p.image||'');
2748      var thumb=imgOk?'<span style="flex:0 0 auto;width:46px;height:46px;border-radius:10px;background:#0c2434 center/cover no-repeat url('+p.image+')"></span>':'';
2749      var b=document.createElement('div'); b.id='projectDirect';
2750      b.setAttribute('style','display:flex;align-items:center;gap:13px;max-width:760px;margin:0 auto 18px;padding:14px 18px;border-radius:14px;background:rgba(168,243,65,.10);border:1px solid rgba(168,243,65,.38)');
2751      b.innerHTML=thumb+'<span style="flex:0 0 auto;display:inline-flex;width:26px;height:26px;align-items:center;justify-content:center;border-radius:50%;background:var(--leaf);color:#04210f;font-weight:900;font-size:15px">&#10003;</span><span><b style="display:block;color:var(--bone);font-size:15.5px">Supporting: '+esc(title)+'</b><span style="font-size:13px;color:var(--mist)">100% of your gift goes to this project'+(ngo?(', delivered by '+esc(ngo)):'')+'. Nothing is taken, and it is not shared with any other fund.</span></span>';
2752      if(shell.parentNode){ shell.parentNode.insertBefore(b, shell); } else { shell.insertBefore(b, shell.firstChild); }
2753    }
2754    document.querySelectorAll('.trail-node h3').forEach(function(h){ if(h.textContent.trim()==='Partner NGO' && ngo) h.textContent=ngo; });
2755    document.title='Donate to '+title+' · Ecoflix Foundation';
2756    if(p.target_usd) window.__ecoProjectGoal(p.funding_usd, p.target_usd, p.target_purpose);
2757    if(p.partner_slug){
2758      fetch('/assets/ngos.json').then(function(r){return r.json();}).then(function(nd){
2759        var ngo=(nd.ngos||[]).find(function(x){return x.id===p.partner_slug;});
2760        if(ngo && ngo.tiers && ngo.tiers.length) window.__ecoApplyTiers(ngo.tiers);
2761      }).catch(function(){});
2762    }
2763  }).catch(function(){});
2764})();
2765</script>
2765
2766
2767<!-- NGO-directed donations: ?ngo=<slug> makes the destination explicit and
2768     removes any general-fund framing. The chosen NGO is also sent to checkout. -->
2769<script>
2770(function(){
2771  function esc(s){return String(s).replace(/[&<>"]/g,function(c){return{'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];});}
2772  var slug=new URLSearchParams(location.search).get('ngo');
2773  if(!slug) return;
2774  fetch('/assets/ngos.json').then(function(r){return r.json();}).then(function(d){
2775    var n=(d.ngos||[]).find(function(x){return x.id===slug;});
2776    if(!n) return;
2777    var name=n.name||'this partner';
2778    window.__ECOFLIX_NGO__={id:n.id,name:name};
2779    var hero=document.querySelector('h1');
2780    if(hero) hero.innerHTML='Your gift goes <em>directly to '+esc(name)+'.</em>';
2781    var lede=document.querySelector('.lede');
2782    if(lede) lede.textContent='100% of your donation routes straight to '+name+', the partner doing the work. Ecoflix takes nothing, and your gift is never pooled into a general fund.';
2783    var shell=document.querySelector('.donate-shell');
2784    if(shell && !document.getElementById('ngoDirect')){
2785      var logo=n.logo?'<span style="flex:0 0 auto;width:46px;height:46px;border-radius:10px;background:#fff center/72% no-repeat url('+n.logo+')"></span>':'';
2786      var b=document.createElement('div');
2787      b.id='ngoDirect';
2788      b.setAttribute('style','display:flex;align-items:center;gap:13px;max-width:760px;margin:0 auto 18px;padding:14px 18px;border-radius:14px;background:rgba(168,243,65,.10);border:1px solid rgba(168,243,65,.38)');
2789      b.innerHTML=logo+'<span style="flex:0 0 auto;display:inline-flex;width:26px;height:26px;align-items:center;justify-content:center;border-radius:50%;background:var(--leaf);color:#04210f;font-weight:900;font-size:15px">&#10003;</span><span><b style="display:block;color:var(--bone);font-size:15.5px">Donating directly to '+esc(name)+'</b><span style="font-size:13px;color:var(--mist)">100% of your gift reaches '+esc(name)+'. Nothing is taken, and it is not shared with any other fund.</span></span>';
2790      if(shell.parentNode){ shell.parentNode.insertBefore(b, shell); } else { shell.insertBefore(b, shell.firstChild); }
2791    }
2792    document.querySelectorAll('.trail-node h3').forEach(function(h){ if(h.textContent.trim()==='Partner NGO') h.textContent=name; });
2793    document.title='Donate to '+name+' · Ecoflix Foundation';
2794    if(n.tiers && n.tiers.length) window.__ecoApplyTiers(n.tiers);
2795  }).catch(function(){});
2796})();
2797</script>
2797
2798
2799  
2799<script src="/assets/nav.js" defer></script>
2799
2800  
2800<script src="/assets/social-proof.js" defer></script>
2800
2801  
2801<script src="/assets/share.js" defer></script>
2801
2802</body>
2803</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.