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> <a href="/terms/">Terms</a> <a href="/cookies/">Cookies</a> <a href="/news/">Press</a> <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{'&':'&','<':'<','>':'>','"':'"'}[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{'&':'&','<':'<','>':'>','"':'"'}[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">✓</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{'&':'&','<':'<','>':'>','"':'"'}[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">✓</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.