1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 5 <title>About Floodlight</title> 6 <meta charset="utf-8"> 7 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 8 9 <link rel="preload" as="style" href="https://floodlightnews.org/assets/built/screen.css?v=sU8q4fMWgA8Y5CRT"> 10 <link rel="preload" as="script" href="https://floodlightnews.org/assets/built/source.js?v=fO0FIOFT6UAWW5WA"> 11 12 <link rel="stylesheet" type="text/css" href="https://floodlightnews.org/assets/built/screen.css?v=sU8q4fMWgA8Y5CRT"> 13 14 <style> 15 :root { 16 --background-color: #fdf8ed 17 } 18 </style> 19 20
20<script> 21 /* The script for calculating the color contrast has been taken from 22 https://gomakethings.com/dynamically-changing-the-text-color-based-on-background-color-contrast-with-vanilla-js/ */ 23 var accentColor = getComputedStyle(document.documentElement).getPropertyValue('--background-color'); 24 accentColor = accentColor.trim().slice(1); 25 var r = parseInt(accentColor.substr(0, 2), 16); 26 var g = parseInt(accentColor.substr(2, 2), 16); 27 var b = parseInt(accentColor.substr(4, 2), 16); 28 var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000; 29 var textColor = (yiq >= 128) ? 'dark' : 'light'; 30 31 document.documentElement.className = `has-${textColor}-text`; 32 </script>
32 33 34 <link rel="icon" href="https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/size/w256h256/2023/12/Logo-Avatar-Small.png" type="image/png"> 35 <link rel="canonical" href="https://floodlightnews.org/about-floodlight/"> 36 <meta name="referrer" content="no-referrer-when-downgrade"> 37 38 <meta property="og:site_name" content="Floodlight"> 39 <meta property="og:type" content="website"> 40 <meta property="og:title" content="About Floodlight"> 41 <meta property="og:description" content="Floodlight is an independent, nonpartisan newsroom that investigates the corporations and political interests stalling climate action. 42 43Through deep investigative reporting, we uncover how powerful interests shape the climate crisis, and who benefits when the public is kept in the dark. 44 45Floodlight exists because there still isn't enough climate journalism,"> 46 <meta property="og:url" content="https://floodlightnews.org/about-floodlight/"> 47 <meta property="og:image" content="https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/size/w1200/2023/12/020421_house_car.png"> 48 <meta property="article:published_time" content="2026-08-05T15:47:42.000Z"> 49 <meta property="article:modified_time" content="2026-10-02T14:11:16.000Z"> 50 <meta name="twitter:card" content="summary_large_image"> 51 <meta name="twitter:title" content="About Floodlight"> 52 <meta name="twitter:description" content="Floodlight is an independent, nonpartisan newsroom that investigates the corporations and political interests stalling climate action. 53 54Through deep investigative reporting, we uncover how powerful interests shape the climate crisis, and who benefits when the public is kept in the dark. 55 56Floodlight exists because there still isn't enough climate journalism,"> 57 <meta name="twitter:url" content="https://floodlightnews.org/about-floodlight/"> 58 <meta name="twitter:image" content="https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/size/w1200/2023/12/020421_house_car.png"> 59 <meta name="twitter:label1" content="Written by"> 60 <meta name="twitter:data1" content="Rosie Gillies/Floodlight"> 61 <meta name="twitter:site" content="@floodlightnews"> 62 <meta property="og:image:width" content="1200"> 63 <meta property="og:image:height" content="613"> 64 65
65<script type="application/ld+json"> 66{ 67 "@context": "https://schema.org", 68 "@type": "Article", 69 "publisher": { 70 "@type": "Organization", 71 "name": "Floodlight", 72 "url": "https://floodlightnews.org/", 73 "logo": { 74 "@type": "ImageObject", 75 "url": "https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/2024/03/Floodlight-Logo-2.png" 76 } 77 }, 78 "author": { 79 "@type": "Person", 80 "name": "Rosie Gillies/Floodlight", 81 "image": { 82 "@type": "ImageObject", 83 "url": "https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/2026/08/rosie-headshot-copy.jpg", 84 "width": 1175, 85 "height": 1175 86 }, 87 "url": "https://floodlightnews.org/author/rosie/", 88 "sameAs": [] 89 }, 90 "headline": "About Floodlight", 91 "url": "https://floodlightnews.org/about-floodlight/", 92 "datePublished": "2026-08-05T15:47:42.000Z", 93 "dateModified": "2026-10-02T14:11:16.000Z", 94 "description": "Floodlight is an independent, nonpartisan newsroom that investigates the corporations and political interests stalling climate action.\n\nThrough deep investigative reporting, we uncover how powerful interests shape the climate crisis, and who benefits when the public is kept in the dark. \n\nFloodlight exists because there still isn't enough climate journalism, and the kind that's hardest to do is the kind least likely to get done. So we do it. We investigate the people and institutions behind the ", 95 "mainEntityOfPage": "https://floodlightnews.org/about-floodlight/" 96} 97 </script>
97 98 99 <meta name="generator" content="Ghost 6.68"> 100 <link rel="alternate" type="application/rss+xml" title="Floodlight" href="https://floodlightnews.org/rss/"> 101
101<script defer src="https://cdn.jsdelivr.net/ghost/portal@~2.71/umd/portal.min.js" data-i18n="true" data-ghost="https://floodlightnews.org/" data-key="260570c761635b8b23e58ec819" data-api="https://floodlight.ghost.io/ghost/api/content/" data-locale="en" crossorigin="anonymous"></script>
101<style id="gh-members-styles">.gh-post-upgrade-cta-content, 102.gh-post-upgrade-cta { 103 display: flex; 104 flex-direction: column; 105 align-items: center; 106 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 107 text-align: center; 108 width: 100%; 109 color: #ffffff; 110 font-size: 16px; 111} 112 113.gh-post-upgrade-cta-content { 114 border-radius: 8px; 115 padding: 40px 4vw; 116} 117 118.gh-post-upgrade-cta h2 { 119 color: #ffffff; 120 font-size: 28px; 121 letter-spacing: -0.2px; 122 margin: 0; 123 padding: 0; 124} 125 126.gh-post-upgrade-cta p { 127 margin: 20px 0 0; 128 padding: 0; 129} 130 131.gh-post-upgrade-cta small { 132 font-size: 16px; 133 letter-spacing: -0.2px; 134} 135 136.gh-post-upgrade-cta a { 137 color: #ffffff; 138 cursor: pointer; 139 font-weight: 500; 140 box-shadow: none; 141 text-decoration: underline; 142} 143 144.gh-post-upgrade-cta a:hover { 145 color: #ffffff; 146 opacity: 0.8; 147 box-shadow: none; 148 text-decoration: underline; 149} 150 151.gh-post-upgrade-cta a.gh-btn { 152 display: block; 153 background: #ffffff; 154 text-decoration: none; 155 margin: 28px 0 0; 156 padding: 8px 18px; 157 border-radius: 4px; 158 font-size: 16px; 159 font-weight: 600; 160} 161 162.gh-post-upgrade-cta a.gh-btn:hover { 163 opacity: 0.92; 164}</style>
164<script async src="https://js.stripe.com/v3/"></script>
164 165
165<script defer src="https://cdn.jsdelivr.net/ghost/sodo-search@~1.8/umd/sodo-search.min.js" data-key="260570c761635b8b23e58ec819" data-styles="https://cdn.jsdelivr.net/ghost/sodo-search@~1.8/umd/main.css" data-sodo-search="https://floodlight.ghost.io/" data-locale="en" crossorigin="anonymous"></script>
165 166 167 <link href="https://floodlightnews.org/webmentions/receive/" rel="webmention"> 168
168<script defer src="/public/cards.min.js?v=ShRHxgy4po8zN-Wf"></script>
168 169 <link rel="stylesheet" type="text/css" href="/public/cards.min.css?v=WwnU9jw5ancNC8Gc"> 170
170<script defer src="/public/member-attribution.min.js?v=AKG4hWena9j3yX3I"></script>
170 171
171<script defer src="/public/ghost-stats.min.js?v=vFcCUf6ZQ0Hyhc8h" data-stringify-payload="false" data-datasource="analytics_events" data-storage="localStorage" data-host="https://floodlightnews.org/.ghost/analytics/api/v1/page_hit" tb_site_uuid="9596ca6b-e09d-4cad-ac1c-03f11680f732" tb_post_uuid="60c311ad-f328-49e1-b415-76e663318204" tb_post_type="page" tb_member_uuid="undefined" tb_member_status="undefined" tb_gift_link=""></script>
171<style>:root {--ghost-accent-color: #1f2e5e;}</style> 172 <!-- Preconnect for analytics --> 173<link rel="preconnect" href="https://plausible.io"> 174<link rel="preconnect" href="https://www.googletagmanager.com"> 175 176<!-- Custom Plausible Tracking -->
177<script> 178(function trackPlausiblePageview() { 179 window.plausible = window.plausible || function () { 180 (window.plausible.q = window.plausible.q || []).push(arguments); 181 }; 182 183 const props = { page_type: 'website' }; 184 const isArticle = document.querySelector('meta[property="og:type"]')?.content === "article"; 185 186 if (isArticle) { 187 props.page_type = 'article'; 188 189 try { 190 let authors = Array.from(document.querySelectorAll(".gh-article-author-name a")) 191 .map(el => el.textContent.trim()) 192 .filter(Boolean) 193 .filter((value, index, self) => self.indexOf(value) === index); 194 195 if (authors.length === 0) { 196 const fallback = document.querySelector('meta[name="twitter:data1"]')?.content; 197 if (fallback) authors = [fallback]; 198 } 199 200 props.author = authors.length ? authors.join(", ") : "Unknown"; 201 props.category = document.querySelector("meta[property='article:tag']")?.content || "Uncategorized"; 202 203 const publishDateRaw = document.querySelector('meta[property="article:published_time"]')?.content; 204 props.publish_date = publishDateRaw ? publishDateRaw.split("T")[0] : "Unknown"; 205 } catch (err) { 206 console.error("Metadata extraction error:", err); 207 } 208 } 209 210 window.plausibleProps = props; 211 plausible("setProps", props); 212 plausible("pageview", { props }); 213 214 if (window.DEBUG) console.log("ð Props set & manual pageview sent:", props); 215})(); 216</script>
216 217 218<!-- Signup Completion Tracking -->
219<script> 220(function confirmSignup() { 221 const props = window.plausibleProps || {}; 222 const pathname = window.location.pathname; 223 const search = window.location.search; 224 225 if (pathname === "/welcome-free-sub/" && search.includes("success=true")) { 226 plausible("NewsletterSignupCompleted", { props }); 227 if (window.DEBUG) console.log("â Fired NewsletterSignupCompleted"); 228 } 229 230 if (pathname === "/welcome-paid-sub/" && search.includes("success=true")) { 231 plausible("MembershipSignupCompleted_paid", { props }); 232 if (window.DEBUG) console.log("â Fired MembershipSignupCompleted_paid"); 233 } 234})(); 235</script>
235 236 237<!-- Plausible Analytics Script -->
238<script defer data-domain="floodlightnews.org" data-auto="false" src="https://plausible.io/js/script.js"></script>
238 239 240<!-- Font Awesome Performance Load --> 241<link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/brands.min.css" 242 as="style" 243 onload="this.onload=null;this.rel='stylesheet'" 244 integrity="sha512-W/zrbCncQnky/EzL+/AYwTtosvrM+YG/V6piQLSe2HuKS6cmbw89kjYkp3tWFn1dkWV7L1ruvJyKbLz73Vlgfg==" 245 crossorigin="anonymous" 246 referrerpolicy="no-referrer"> 247<noscript> 248 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/brands.min.css" 249 integrity="sha512-W/zrbCncQnky/EzL+/AYwTtosvrM+YG/V6piQLSe2HuKS6cmbw89kjYkp3tWFn1dkWV7L1ruvJyKbLz73Vlgfg==" 250 crossorigin="anonymous" 251 referrerpolicy="no-referrer"> 252</noscript> 253 254<style> 255 .home-template .banner { padding: 0; aspect-ratio: 3.65/1; } 256 257 .nav .nav-instagram a, 258 .nav .nav-linkedin a, 259 .nav .nav-threads a, 260 .nav .nav-twitter a, 261 .nav .nav-youtube a { 262 font-size: 0 !important; 263 } 264 265 .nav .nav-instagram a::before, 266 .nav .nav-linkedin a::before, 267 .nav .nav-threads a::before, 268 .nav .nav-twitter a::before, 269 .nav .nav-youtube a::before { 270 font-family: "Font Awesome 6 Brands"; 271 display: inline-block; 272 font-size: 20px; 273 font-style: normal; 274 font-weight: normal; 275 font-variant: normal; 276 text-rendering: auto; 277 -webkit-font-smoothing: antialiased; 278 } 279 280 .nav .nav-instagram a::before {content: "\f16d"} 281 .nav .nav-linkedin a::before {content: "\f08c"} 282 .nav .nav-threads a::before {content: "\e618"} 283 .nav .nav-twitter a::before {content: "\f099"} 284 .nav .nav-youtube a::before {content: "\f167"} 285 286 .gh-header.is-highlight .gh-header-middle .gh-card .gh-card-excerpt, 287 .gh-header.is-highlight .gh-header-middle .gh-card:last-child .gh-card-image { 288 display: none; 289 } 290</style> 291<meta name="robots" content="max-image-preview:large"> 292 293<style> 294 .gh-article-author-image a { 295 display: block !important; 296 position: relative !important; 297 line-height: 0; 298 overflow: hidden; 299 } 300 .gh-article-author-image :is(img, svg) { 301 width: 100% !important; 302 height: 100% !important; 303 } 304 .about-author .gh-article-image img, 305 .author-template .gh-article-image img { 306 width: 120px !important; 307 height: 120px !important; 308 object-fit: cover !important; 309 object-position: center top !important; 310 } 311</style> 312<!-- Start cookieyes banner -->
312<script id="cookieyes" type="text/javascript" src="https://cdn-cookieyes.com/client_data/52eaa53177139c08457bc49c7930ae1b/script.js"></script>
312 <!-- End cookieyes banner --> 313 314<!-- =================================================================== 315 FLOODLIGHT BANNER + STICKY BAR (with inline email signup) 316 Paste into Ghost admin: Settings -> Code injection -> Site footer 317 318 FIRST: turn OFF Ghost's own announcement bar 319 Settings -> Site -> Announcement bar -> clear the text. 320 Ghost renders that bar in an iframe, so its close button can't be 321 removed from here. This replaces it. 322 323 Parts: 324 1. .fl-banner - navy bar at the top of the page, centred text 325 2. .fl-stickybar - condensed bar that slides down once you scroll 326 past the nav: logo, text, email signup, Donate 327 =================================================================== --> 328 329<style> 330 /* --------------------------------------------------------------- 331 TOP BANNER - navy, centred, no close button 332 --------------------------------------------------------------- */ 333 .fl-banner{ 334 width:100%; 335 box-sizing:border-box; 336 padding:1.15rem 3rem; 337 background:#1F2E5E; 338 color:#fff; 339 text-align:center; 340 font-size:1.4rem; 341 font-weight:700; 342 line-height:1.35; 343 font-family:inherit; 344 } 345 .fl-banner a{color:inherit;text-decoration:underline;text-underline-offset:2px} 346 347 /* --------------------------------------------------------------- 348 STICKY BAR 349 Light by default so the official logo shows in its real colours. 350 Add class "is-dark" (see STICKY_STYLE in the script) for navy. 351 --------------------------------------------------------------- */ 352 .fl-stickybar{ 353 --fl-navy:#1F2E5E; 354 --fl-yellow:#FDF351; 355 --fl-cream:#FDF8ED; 356 --fl-blue:#C1C8E1; /* periwinkle from the Floodlight illustration */ 357 358 position:fixed; 359 top:0;left:0;right:0; 360 z-index:1000; 361 box-sizing:border-box; 362 display:flex; 363 align-items:center; 364 gap:clamp(.6rem,1.5vw,1.25rem); 365 /* Bar height comes from this padding - raise it for a taller bar */ 366 padding:1.05rem clamp(1rem,2.5vw,1.75rem); 367 font-family:inherit; 368 369 background:var(--fl-cream); 370 color:var(--fl-navy); 371 border-bottom:1px solid rgba(31,46,94,.14); 372 box-shadow:0 2px 14px rgba(0,0,0,.08); 373 374 transform:translateY(-100%); 375 visibility:hidden; 376 transition:transform .22s ease, visibility .22s; 377 } 378 .fl-stickybar.is-visible{transform:translateY(0);visibility:visible} 379 380 .fl-stickybar.is-dark{ 381 background:var(--fl-navy); 382 color:#fff; 383 border-bottom-color:rgba(255,255,255,.16); 384 } 385 /* The icon logo has its own colours, so no filter is applied. 386 If you ever swap back to the navy wordmark on a dark bar, add: 387 .fl-stickybar.is-dark .fl-sb-logo img{filter:brightness(0) invert(1)} */ 388 389 /* ---- logo ---- */ 390 .fl-sb-logo{display:flex;align-items:center;flex-shrink:0} 391 .fl-sb-logo img{height:32px;width:auto;display:block} 392 393 /* ---- text ---- */ 394 .fl-sb-text{ 395 flex:0 1 auto;min-width:0; 396 text-align:left; 397 font-size:1.4rem;font-weight:700;line-height:1.3; 398 overflow:hidden;text-overflow:ellipsis;white-space:nowrap; 399 } 400 401 /* ---- right-hand group: email signup + donate ---- */ 402 .fl-sb-actions{ 403 margin-left:auto; /* pins the group to the right edge */ 404 display:flex; 405 align-items:center; 406 gap:.6rem; 407 flex-shrink:0; 408 } 409 410 /* ---- inline email signup ---- */ 411 .fl-sb-form{margin:0;flex-shrink:0;display:flex;align-items:center} 412 .fl-sb-fields{display:flex;align-items:center;gap:.4rem} 413 .fl-sb-form,.fl-sb-actions,.fl-sb-fields{align-items:center} 414 .fl-sb-input{ 415 /* Width of the email box - raise these to make it wider still */ 416 width:clamp(13rem,24vw,22rem); 417 box-sizing:border-box; 418 padding:.7rem 1rem; 419 font:inherit;font-size:1rem; 420 color:var(--fl-navy); 421 background:#fff; 422 border:1px solid rgba(31,46,94,.3); 423 border-radius:999px; 424 } 425 .fl-sb-input::placeholder{color:#7A83A0} 426 .fl-sb-input:focus-visible{outline:2px solid var(--fl-navy);outline-offset:1px} 427 .fl-stickybar.is-dark .fl-sb-input{border-color:rgba(255,255,255,.35)} 428 .fl-stickybar.is-dark .fl-sb-input:focus-visible{outline-color:var(--fl-yellow)} 429 430 .fl-sb-btn{ 431 display:inline-flex;align-items:center;justify-content:center; 432 box-sizing:border-box; 433 padding:.7rem 1.35rem; 434 font:inherit;font-size:1.05rem;font-weight:700; 435 border:0;border-radius:999px; 436 cursor:pointer;white-space:nowrap;line-height:1.2; 437 text-decoration:none; 438 } 439 .fl-sb-submit{ 440 background:var(--fl-blue); 441 color:var(--fl-navy); 442 /* matches your global a.donate-btn[href] rule */ 443 padding:11px 22px; 444 border-radius:6px; 445 font-size:1.05rem; 446 font-weight:700; 447 } 448 .fl-sb-submit:hover{background:#AEB7D6;color:var(--fl-navy)} 449 .fl-stickybar.is-dark .fl-sb-submit{background:var(--fl-blue);color:var(--fl-navy)} 450 .fl-stickybar.is-dark .fl-sb-submit:hover{background:#AEB7D6;color:var(--fl-navy)} 451 452 .fl-sb-donate{background:var(--fl-yellow);
452color:#1a1a1a;flex-shrink:0} 453 /* Your global a.donate-btn[href] rule sets font-weight:600 !important, 454 so beating it needs both specificity and !important. */ 455 .fl-stickybar a.fl-sb-donate[href]{ 456 padding:11px 22px !important; 457 border-radius:6px !important; 458 font-size:1.05rem !important; 459 font-weight:700 !important; 460 } 461 .fl-sb-donate:hover{background:#ECE234;color:#1a1a1a} 462 463 .fl-sb-btn:focus-visible{outline:2px solid var(--fl-navy);outline-offset:2px} 464 .fl-stickybar.is-dark .fl-sb-btn:focus-visible{outline-color:var(--fl-yellow)} 465 466 /* Ghost toggles .loading / .success / .error on the form */ 467 .fl-sb-loading{display:none} 468 .fl-sb-form.loading .fl-sb-default{display:none} 469 .fl-sb-form.loading .fl-sb-loading{display:inline} 470 .fl-sb-msg{display:none;font-size:.9rem;font-weight:600;white-space:nowrap} 471 .fl-sb-form.success .fl-sb-fields{display:none} 472 .fl-sb-form.success .fl-sb-success{display:inline} 473 .fl-sb-error:not(:empty){display:inline;font-weight:500;color:#B3391F} 474 .fl-stickybar.is-dark .fl-sb-error:not(:empty){color:#F5C4B3} 475 476 /* --------------------------------------------------------------- 477 RESPONSIVE - shed the least important things first 478 --------------------------------------------------------------- */ 479 /* Below this the tagline can't sit alongside the form without 480 truncating to nonsense, so it drops. Raise or lower to taste. */ 481 @media (max-width:980px){ 482 .fl-sb-text{display:none} 483 } 484 /* Not enough room for the form: keep logo + Donate */ 485 @media (max-width:820px){ 486 .fl-banner{padding:.95rem 1rem;font-size:1.05rem} 487 .fl-sb-form{display:none} 488 .fl-sb-logo img{height:28px} 489 .fl-stickybar{gap:.6rem;padding:.85rem .8rem;justify-content:space-between} 490 .fl-sb-btn{padding:.55rem 1rem;font-size:.95rem} 491 } 492 493 @media (prefers-reduced-motion:reduce){ 494 .fl-stickybar{transition:none} 495 } 496 497 /* Backstop: hide Ghost's announcement bar if it's still enabled */ 498 #announcement-bar-root, 499 [data-ghost-announcement-bar]{display:none !important} 500</style> 501
502<script> 503(function(){ 504 "use strict"; 505 506 /* Set to true to log what it found, in the browser console. */ 507 var DEBUG = false; 508 509 /* ===================================================================== 510 CONFIG 511 ===================================================================== */ 512 513 /* Banner text, used in both bars. */ 514 var TEXT = "Nonprofit investigative journalism on the powers stalling climate action"; 515 516 /* Optional link on the banner text. "" for plain text. */ 517 var TEXT_LINK = ""; 518 519 /* Official Floodlight logo. */ 520 var LOGO = "https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/2026/08/floodlightlogo-01-2--2--1.png"; 521 522 /* "light" = cream bar, logo in its real colours (recommended) 523 "dark" = navy bar, logo inverted to white */ 524 var STICKY_STYLE = "dark"; 525 526 /* Email signup */ 527 var SHOW_EMAIL = true; 528 var EMAIL_PLACEHOLDER = "Your email"; 529 var EMAIL_BUTTON = "Sign Up"; 530 var EMAIL_SUCCESS = "Check your inbox"; 531 /* Shows in Ghost admin so you can see which signups came from here */ 532 var SIGNUP_LABEL = "sticky-bar"; 533 534 535 /* Donate button */ 536 var SHOW_DONATE = true; 537 var DONATE_URL = "https://floodlightnews.org/donate-to-floodlight/"; 538 var DONATE_LABEL = "Donate"; 539 540 /* Show the top banner as well? false = sticky bar only. */ 541 var SHOW_TOP_BANNER = true; 542 543 /* ===================================================================== */ 544 545 function esc(s){ 546 return String(s == null ? "" : s).replace(/[&<>"']/g, function(c){ 547 return { "&":"&", "<":"<", ">":">", '"':""", "'":"'" }[c]; 548 }); 549 } 550 551 function textHtml(){ 552 var t = esc(TEXT); 553 return TEXT_LINK ? '<a href="' + esc(TEXT_LINK) + '">' + t + '</a>' : t; 554 } 555 556 function build(){ 557 if (document.getElementById("fl-stickybar")) return; 558 559 var header = document.getElementById("gh-navigation") || 560 document.querySelector("header"); 561 if (!header){ 562 if (DEBUG) console.warn("[fl-banner] header not found"); 563 return; 564 } 565 566 /* ---- top banner ---- */ 567 if (SHOW_TOP_BANNER && !document.getElementById("fl-banner")){ 568 var banner = document.createElement("div"); 569 banner.id = "fl-banner"; 570 banner.className = "fl-banner"; 571 banner.setAttribute("role", "region"); 572 banner.setAttribute("aria-label", "Announcement"); 573 banner.innerHTML = textHtml(); 574 header.parentNode.insertBefore(banner, header); 575 } 576 577 /* ---- sticky bar ---- */ 578 var bar = document.createElement("div"); 579 bar.id = "fl-stickybar"; 580 bar.className = "fl-stickybar" + (STICKY_STYLE === "dark" ? " is-dark" : ""); 581 582 var html = '' 583 + '<a class="fl-sb-logo" href="/" aria-label="Floodlight home">' 584 + '<img src="' + esc(LOGO) + '" alt="Floodlight">' 585 + '</a>' 586 + '<div class="fl-sb-text">' + textHtml() + '</div>'; 587 588 html += '<div class="fl-sb-actions">'; 589 590 if (SHOW_EMAIL){ 591 html += '' 592 + '<form class="fl-sb-form" data-members-form="signup">' 593+ '<input type="hidden" data-members-label value="' + esc(SIGNUP_LABEL) + '">' 594+ '<div class="fl-sb-fields">' 595 + '<label for="fl-sb-email" style="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)">Email address</label>' 596 + '<input class="fl-sb-input" id="fl-sb-email" type="email" required data-members-email placeholder="' + esc(EMAIL_PLACEHOLDER) + '">' 597 + '<button class="fl-sb-btn fl-sb-submit" type="submit">' 598 + '<span class="fl-sb-default">' + esc(EMAIL_BUTTON) + '</span>' 599 + '<span class="fl-sb-loading">Sending…</span>' 600 + '</button>' 601 + '</div>' 602 + '<span class="fl-sb-msg fl-sb-success" role="status">' + esc(EMAIL_SUCCESS) + '</span>' 603 + '<span class="fl-sb-msg fl-sb-error" role="alert" data-members-error></span>' 604 + '</form>'; 605 } 606 607 if (SHOW_DONATE){ 608 /* .nav-donate wrapper matches your existing Plausible selector */ 609 html += '' 610 + '<span class="nav-donate">' 611 + '<a class="fl-sb-btn fl-sb-donate" href="' + esc(DONATE_U
611RL) + '">' 612 + esc(DONATE_LABEL) 613 + '</a>' 614 + '</span>'; 615 } 616 617 html += '</div>'; /* close .fl-sb-actions */ 618 619 bar.innerHTML = html; 620 document.body.appendChild(bar); 621 622 /* ---- reveal once the header has scrolled out of view ---- */ 623 function show(state){ bar.classList.toggle("is-visible", state); } 624 625 if ("IntersectionObserver" in window){ 626 new IntersectionObserver(function(entries){ 627 show(!entries[0].isIntersecting); 628 if (DEBUG) console.log("[fl-banner] sticky:", !entries[0].isIntersecting); 629 }, { threshold: 0 }).observe(header); 630 } else { 631 window.addEventListener("scroll", function(){ 632 show(header.getBoundingClientRect().bottom <= 0); 633 }, { passive: true }); 634 } 635 636 if (DEBUG) console.log("[fl-banner] built"); 637 } 638 639 if (document.readyState === "loading"){ 640 document.addEventListener("DOMContentLoaded", build); 641 } else { 642 build(); 643 } 644})(); 645</script>
645 646 647 648<!-- =================================================================== 649 SIGN IN -> DONATE 650 Replaces Ghost's Sign In link with a normal donation link. 651 =================================================================== --> 652 653<style> 654 a.donate-btn { 655 display:inline-flex; 656 align-items:center; 657 justify-content:center; 658 background:#fdf351 !important; 659 color:#1a1a1a !important; 660 padding:7px 16px !important; 661 border-radius:6px !important; 662 font-weight:600 !important; 663 font-family:inherit; 664 line-height:1.2; 665 border:0; 666 cursor:pointer; 667 text-decoration:none; 668 } 669 670 a.donate-btn:hover { 671 background:#ece234 !important; 672 } 673 674 a.donate-btn:focus-visible { 675 outline:2px solid #1F2E5E; 676 outline-offset:2px; 677 } 678</style> 679
680<script> 681(function(){ 682 "use strict"; 683 684 var DONATE_URL = "https://floodlightnews.org/donate-to-floodlight/"; 685 var LABEL = "Donate"; 686 687 var SELECTORS = [ 688 'a[href="#/portal/signin"]', 689 'a[href$="#/portal/signin"]', 690 'a[data-portal="signin"]' 691 ].join(","); 692 693 function patch(){ 694 document.querySelectorAll(SELECTORS).forEach(function(a){ 695 696 if (a.dataset.flDone) return; 697 698 /* Copy the original link's computed typography */ 699 var cs = window.getComputedStyle(a); 700 701 var link = document.createElement("a"); 702 703 /* Keep the theme's classes for positioning/styling */ 704 link.className = (a.className ? a.className + " " : "") + "donate-btn"; 705 706 /* IMPORTANT: 707 Do NOT copy data-portal="signin". 708 Do NOT leave the old #/portal/signin href. 709 */ 710 link.href = DONATE_URL; 711 link.textContent = LABEL; 712 link.dataset.flDone = "1"; 713 714 link.style.fontSize = cs.fontSize; 715 link.style.fontFamily = cs.fontFamily; 716 link.style.letterSpacing = cs.letterSpacing; 717 link.style.lineHeight = cs.lineHeight === "normal" ? "1.2" : cs.lineHeight; 718 719 /* Replace the Ghost Sign In link with a completely normal link */ 720 a.replaceWith(link); 721 }); 722 } 723 724 function start(){ 725 patch(); 726 727 /* Ghost builds the mobile navigation after initial load, 728 so keep watching for newly-created Sign In links. */ 729 if (window.MutationObserver && document.body){ 730 var pending = null; 731 732 new MutationObserver(function(){ 733 clearTimeout(pending); 734 pending = setTimeout(patch, 60); 735 }).observe(document.body, { 736 childList: true, 737 subtree: true 738 }); 739 } 740 } 741 742 if (document.readyState === "loading"){ 743 document.addEventListener("DOMContentLoaded", start); 744 } else { 745 start(); 746 } 747 748})(); 749</script>
749 750 751 752
753<script> 754(function () { 755 var DEST = 'https://secure.qgiv.com/for/floodlightcatchall'; 756 var MATCH = /^#\/portal\/support/; 757 758 function trap() { 759 if (MATCH.test(window.location.hash)) window.location.replace(DEST); 760 } 761 762 trap(); 763 window.addEventListener('hashchange', trap); 764 765 document.addEventListener('click', function (e) { 766 var el = e.target.closest('a[href*="#/portal/support"], [data-portal="support"]'); 767 if (!el) return; 768 e.preventDefault(); 769 e.stopPropagation(); 770 window.location.href = DEST; 771 }, true); 772})(); 773</script>
773 774 775</head> 776<body class="page-template page-about-floodlight has-sans-title has-sans-body"> 777 778<div class="gh-viewport"> 779 780 <header id="gh-navigation" class="gh-navigation is-stacked gh-outer"> 781 <div class="gh-navigation-inner gh-inner"> 782 783 <div class="gh-navigation-brand"> 784 <a class="gh-navigation-logo is-title" href="https://floodlightnews.org"> 785 <img src="https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/2024/03/Floodlight-Logo-2.png" alt="Floodlight"> 786 </a> 787 <button class="gh-search gh-icon-button" aria-label="Search this site" data-ghost-search> 788 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" width="20" height="20"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg></button> <button class="gh-burger gh-icon-button" aria-label="Menu"> 789 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M224,128a8,8,0,0,1-8,8H40a8,8,0,0,1,0-16H216A8,8,0,0,1,224,128ZM40,72H216a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16ZM216,184H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Z"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 256 256"><path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"></path></svg> </button> 790 </div> 791 792 <nav class="gh-navigation-menu"> 793 <ul class="nav"> 794 <li class="nav-about nav-current"><a href="https://floodlightnews.org/about-floodlight/">About</a></li> 795 <li class="nav-projects"><a href="https://floodlightnews.org/projects/">Projects</a></li> 796 <li class="nav-republish-our-work"><a href="https://floodlightnews.org/republish-us/">Republish Our Work</a></li> 797 <li class="nav-newsletter"><a href="https://floodlightnews.org/sign-up-for-our-newsletter/">Newsletter</a></li> 798 <li class="nav-watch"><a href="https://www.youtube.com/@floodlightnews">Watch </a></li> 799</ul> 800 801 </nav> 802 803 <div class="gh-navigation-actions"> 804 <button class="gh-search gh-icon-button" aria-label="Search this site" data-ghost-search> 805 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" width="20" height="20"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg></button> <div class="gh-navigation-members"> 806 <a href="#/portal/signin" data-portal="signin">Sign in</a> 807 <a class="gh-button" href="#/portal/signup" data-portal="signup">Subscribe</a> 808 </div> 809 </div> 810 811 </div> 812</header> 813 814 815 816<main class="gh-main"> 817 <article class="gh-article post no-image"> 818 819 <header class="gh-article-header gh-canvas"> 820 <h1 class="gh-article-title is-title">About Floodlight</h1> 821 </header> 822 823 <section class="gh-content gh-canvas is-body"> 824 <p>Floodlight is an independent, nonpartisan newsroom that investigates the corporations and political interests <strong>stalling climate action</strong>. </p><p>Through deep investigative reporting, we uncover how <strong>powerful interests shape the climate crisis</strong>, and who benefits when the public is kept in the dark. </p><p>Floodlight exists because there still isn't enough climate journalism, and the kind that's hardest to do is the kind least likely to get done. <strong>So we do it.</strong> We investigate the people and institutions behind the fossil fuel industry â the utility executives, trade groups, lobbyists, consultants and elected officials who shape what's possible on climate, usually out of public view. </p><p><strong>And we don't keep it.</strong> Every story we report is published with partner newsrooms across the country, because a story that stays in one place doesn't change much.</p><p>Read more about our <a href="https://floodlightnews.org/about/" rel="noreferrer">mission and model</a>.</p> 825<!--kg-card-begin: html--> 826<!-- SPLIT CTA - ABOUT version. Ghost label: split-widget-about | Qgiv utm_source: split-widget-about --> 827<style> 828/* ---------- Full-bleed breakout: escapes the text column ---------- */ 829.fl-split{ 830 width:100vw; 831 margin-left:calc(50% - 50vw); 832 margin-right:calc(50% - 50vw); 833 max-width:100vw; 834 border-top:3px solid var(--fl-beam); 835 border-radius:0; 836 overflow:hidden; 837 display:grid; 838 grid-template-columns:1fr 1fr; 839 font-family:inherit; 840 --fl-sky:#C1C8E1;--fl-slate:#8A8F9F;--fl-navy:#1F2E5E;--fl-beam:#FFF200; 841} 842.fl-half{padding:clamp(2rem,4vw,3.5rem) clamp(1.5rem,3.5vw,3rem);display:flex;justify-content:center} 843.fl-inner{width:100%;max-width:46rem} 844/* Illustration along the bottom of the newsletter half. Its sky is the 845 same blue as the panel, so it blends with no visible edge. Adjust 846 --fl-art-h to show more or less of it. */ 847.fl-half--news{ 848 --fl-art-h:190px; /* visual height of the artwork strip */ 849 --fl-overlap:80px; /* how far the text sits over it - the top of 850 the image is sky, so this is invisible */ 851 background-color:var(--fl-sky); 852 background-image:url('https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/size/w1600/2026/08/Presentation1-1.png'); 853 background-repeat:no-repeat; 854 background-position:center bottom; 855 background-size:100% auto; 856 color:var(--fl-navy);
857 padding-bottom:calc(clamp(2rem,4vw,3.5rem) + var(--fl-art-h) - var(--fl-overlap)); 858} 859.fl-half--give{background:var(--fl-navy);color:#fff} 860.fl-eyebrow{margin:0 0 .9rem;font-size:.8rem;letter-spacing:.14em;text-transform:uppercase} 861.fl-half--news .fl-eyebrow{color:#4A5578} 862.fl-half--give .fl-eyebrow{color:var(--fl-beam)} 863.fl-split h2{margin:0 0 .8rem;font-size:clamp(1.6rem,3vw,2.6rem);line-height:1.15;color:inherit} 864.fl-split p{margin:0;font-size:clamp(1.05rem,1.35vw,1.25rem);line-height:1.55} 865.fl-half--news p{color:#3A4670} 866.fl-half--give p{color:rgba(255,255,255,.75)} 867.fl-lede{margin-bottom:1.75rem!important} 868.fl-fine{margin-top:1.1rem!important;font-size:.9rem} 869.fl-half--news .fl-fine{color:#5A6488} 870.fl-half--give .fl-fine{color:rgba(255,255,255,.55)} 871.fl-split .kg-signup-card-fields{display:flex;gap:.5rem} 872.fl-split .kg-signup-card-input{flex:1;min-width:0;padding:1rem 1.1rem;font:inherit;font-size:1.1rem;color:var(--fl-navy);background:#fff;border:1px solid rgba(31,46,94,.28);border-radius:4px} 873.fl-split .kg-signup-card-input::placeholder{color:#7A83A0} 874.fl-split .kg-signup-card-input:focus-visible{outline:2px solid var(--fl-navy);outline-offset:1px} 875.fl-split .kg-signup-card-button{display:inline-flex;align-items:center;justify-content:center;min-width:9rem;padding:1rem 1.5rem;font:inherit;font-size:1.1rem;font-weight:600;color:#fff;background:var(--fl-navy);border:0;border-radius:4px;cursor:pointer} 876.fl-split .kg-signup-card-button:hover{background:#16224A} 877.fl-split .kg-signup-card-button:focus-visible{outline:2px solid var(--fl-navy);outline-offset:2px} 878.fl-split .kg-signup-card-button-loading{display:none} 879.fl-split .kg-signup-card-form.loading .kg-signup-card-button-default{display:none} 880.fl-split .kg-signup-card-form.loading .kg-signup-card-button-loading{display:flex} 881.fl-split .kg-signup-card-success,.fl-split .kg-signup-card-error{display:none;margin-top:.8rem;font-size:.95rem;line-height:1.5} 882.fl-split .kg-signup-card-form.success .kg-signup-card-fields{display:none} 883.fl-split .kg-signup-card-form.success .kg-signup-card-success{display:block;font-weight:600;color:var(--fl-navy)} 884.fl-split .kg-signup-card-error:not(:empty){display:block;color:#8C2A15} 885 886/* ---------- Frequency toggle ---------- */ 887.fl-freq{display:inline-flex;border:1px solid rgba(255,255,255,.25);border-radius:4px;overflow:hidden;margin-bottom:.8rem} 888.fl-freq button{border:0;border-radius:0;padding:.65rem 1.15rem;font:inherit;font-size:1rem;font-weight:600;background:transparent;color:rgba(255,255,255,.75);cursor:pointer} 889.fl-freq button + button{border-left:1px solid rgba(255,255,255,.2)} 890.fl-freq button:hover{background:rgba(255,255,255,.08)} 891.fl-freq button[aria-pressed="true"]{background:var(--fl-beam);color:var(--fl-navy)} 892.fl-freq button:focus-visible{outline:2px solid var(--fl-beam);outline-offset:-2px} 893 894.fl-amounts{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.5rem;margin-bottom:.6rem} 895.fl-amt{padding:1rem .2rem;font:inherit;font-size:1.1rem;color:#fff;background:transparent;border:1px solid rgba(255,255,255,.22);border-radius:4px;cursor:pointer} 896.fl-amt:hover{background:rgba(255,255,255,.08)} 897.fl-amt[aria-pressed="true"]{color:var(--fl-beam);border-color:var(--fl-beam);background:rgba(255,242,0,.1)} 898.fl-amt:focus-visible,.fl-give:focus-visible,.fl-custom:focus-visible{outline:2px solid var(--fl-beam);outline-offset:1px} 899.fl-custom{display:none;width:100%;box-sizing:border-box;margin-bottom:.6rem;padding:1rem 1.1rem;font:inherit;font-size:1.1rem;color:#fff;background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.22);border-radius:4px} 900.fl-custom::placeholder{color:rgba(255,255,255,.5)} 901.fl-give{display:block;width:100%;box-sizing:border-box;padding:1.05rem 1.5rem;font-size:1.15rem;font-weight:600;text-align:center;text-decoration:none;color:var(--fl-navy);background:var(--fl-beam);border-radius:4px} 902.fl-give:hover{background:#E6DA00;color:var(--fl-navy)} 903@media (max-width:800px){ 904 .fl-split{grid-template-columns:1fr} 905 .fl-half--news{--fl-art-h:130px;--fl-overlap:55px} 906} 907@media (prefers-reduced-motion:reduce){.fl-split .nc-loop-dots-4-24-icon-o *{animation:none}} 908</style> 909 910<div class="fl-split fl-split--about"> 911 912 <div class="fl-half fl-half--news"> 913 <div class="fl-inner"> 914 <p class="fl-eyebrow">Newsletter</p> 915 <h2>The climate crisis has a backstory. We uncover it.</h2> 916 <p class="fl-lede">Original investigations into how corporate influence and political decisions are delaying action — twice a month, straight to your inbox.</p> 917 918 <form class="kg-signup-card-form" data-members-form="signup"> 919 <input data-members-label="" type="hidden" value="split-widget-about"> 920 <input data-members-label="" type="hidden" value="about-page"> 921 <input data-members-label="" type="hidden" value="#readernewsletter"> 922 <div class="kg-signup-card-fields"> 923 <label for="fl-email" style="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)">Email address</label> 924 <input class="kg-signup-card-input" id="fl-email" data-members-email type="email" required="true" placeholder="[email protected]"> 925 <button class="kg-signup-card-button" type="submit">
926 <span class="kg-signup-card-button-default">Subscribe</span> 927 <span class="kg-signup-card-button-loading"><svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24" aria-hidden="true"><g stroke-linecap="round" stroke-width="2" fill="currentColor" stroke="none" stroke-linejoin="round" class="nc-icon-wrapper"><g class="nc-loop-dots-4-24-icon-o"><circle cx="4" cy="12" r="3"></circle><circle cx="12" cy="12" r="3"></circle><circle cx="20" cy="12" r="3"></circle></g><style data-cap="butt">.nc-loop-dots-4-24-icon-o{--animation-duration:0.8s}.nc-loop-dots-4-24-icon-o *{opacity:.4;transform:scale(.75);animation:nc-loop-dots-4-anim var(--animation-duration) infinite}.nc-loop-dots-4-24-icon-o :nth-child(1){transform-origin:4px 12px;animation-delay:calc(var(--animation-duration)/-2.666)}.nc-loop-dots-4-24-icon-o :nth-child(2){transform-origin:12px 12px;animation-delay:calc(var(--animation-duration)/-5.333)}.nc-loop-dots-4-24-icon-o :nth-child(3){transform-origin:20px 12px}@keyframes nc-loop-dots-4-anim{0%,100%{opacity:.4;transform:scale(.75)}50%{opacity:1;transform:scale(1)}}</style></g></svg></span> 928 </button> 929 </div> 930 <div class="kg-signup-card-success" role="status">Email sent! Check your inbox to complete your signup.</div> 931 <div class="kg-signup-card-error" role="alert" data-members-error></div> 932 </form> 933 934 <p class="fl-fine">No spam. Unsubscribe anytime.</p> 935 </div> 936 </div> 937 938 <div class="fl-half fl-half--give"> 939 <div class="fl-inner"> 940 <p class="fl-eyebrow">Support</p> 941 <h2>The industries we cover have unlimited resources. We have <i>you.</i></h2> 942 <p class="fl-lede">Reader funding is what lets us start an investigation that takes six months and see it through — and share it free with any newsroom that wants to run it.</p> 943 944 <div class="fl-freq" id="fl-freq" role="group" aria-label="Giving frequency"> 945 <button type="button" data-freq="onetime" aria-pressed="true">One time</button> 946 <button type="button" data-freq="recurring" aria-pressed="false">Recurring</button> 947 </div> 948 949 <div class="fl-amounts" role="group" aria-label="Donation amount"> 950 <button type="button" class="fl-amt" data-amt="50" aria-pressed="true">$50</button> 951 <button type="button" class="fl-amt" data-amt="100" aria-pressed="false">$100</button> 952 <button type="button" class="fl-amt" data-amt="250" aria-pressed="false">$250</button> 953 <button type="button" class="fl-amt" data-amt="0" aria-pressed="false">Other</button> 954 </div> 955 <label for="fl-custom" style="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)">Custom amount in dollars</label> 956 <input class="fl-custom" id="fl-custom" type="number" min="1" step="1" inputmode="numeric" placeholder="Enter amount"> 957 958 <a class="fl-give" id="fl-give" href="#">Donate $50</a> 959 960 <p class="fl-fine" id="fl-note">Floodlight is a 501(c)3 nonprofit and all gifts are tax-deductible.</p> 961 </div> 962 </div> 963 964</div> 965
966<script> 967(function(){ 968 969 /* ===================================================================== 970 BLOOMERANG / QGIV 971 972 Confirmed working on your form: 973 https://secure.qgiv.com/for/floodlight/onetime?otherAmount=73 974 https://secure.qgiv.com/for/floodlight/recurring?otherAmount=73 975 976 Every button builds: BASE + "/" + frequency + "?otherAmount=" + amount 977 Any figure works - these do NOT need to match giving levels set up 978 in Qgiv, because otherAmount= accepts arbitrary values. 979 ===================================================================== */ 980 981 var QGIV_BASE = "https://secure.qgiv.com/for/floodlight"; 982 var AMOUNT_PARAM = "otherAmount"; 983 984 /* Qgiv giving-level IDs. Using these makes the amount land as a 985 SELECTED preset on the donation form. otherAmount= works for any 986 figure but Qgiv treats it as a custom "other" amount, which is why 987 $50 was showing up in the Other box. 988 989 The same ID covers one-time and recurring - only the last segment 990 of the URL changes. */ 991 var AMOUNT_IDS = { 992 50: "1914790", 993 100: "1914791", 994 250: "1914792" 995 }; 996 997 /* Which toggle starts selected: "onetime" or "recurring". */ 998 var DEFAULT_FREQ = "onetime"; 999 1000 /* Appended so you can see which gifts came from this widget. 1001 Set to "" to switch off. */ 1002 var TRACKING = "utm_source=split-widget-about"; 1003 1004 /* The line under the Donate button, per frequency. */ 1005 var NOTES = { 1006 onetime: "Floodlight is a 501(c)3 nonprofit and all gifts are tax-deductible.", 1007 recurring: "Choose how often you give on the next page. Cancel any time." 1008 }; 1009 1010 /* ===================================================================== */ 1011 1012 var root = document.querySelector(".fl-split--about"); 1013 if (!root || root.dataset.flReady) return; 1014 root.dataset.flReady = "1"; 1015 1016 var btns = root.querySelectorAll(".fl-amt"); 1017 var freqWrap = root.querySelector("#fl-freq"); 1018 var custom = root.querySelector("#fl-custom"); 1019 var give = root.querySelector("#fl-give"); 1020 var note = root.querySelector("#fl-note"); 1021 var sel = 50; 1022 var freq = DEFAULT_FREQ; 1023 1024 function buildUrl(amount){ 1025 var url; 1026 var id = amount ? AMOUNT_IDS[amount] : null; 1027 1028 if (id){ 1029 /* a configured giving level - arrives preselected */ 1030 url = QGIV_BASE + "/amount/" + id + "/" + freq; 1031 } else if (amount){ 1032 /* anything typed into Other */ 1033 url = QGIV_BASE + "/" + freq 1034 + "?" + encodeURIComponent(AMOUNT_PARAM) + "=" + encodeURIComponent(amount); 1035 } else { 1036 /* Other picked, nothing typed yet */ 1037 url = QGIV_BASE + "/" + freq; 1038 } 1039 1040 if (TRACKING){ 1041 url += (url.indexOf("?") > -1 ? "&" : "?") + TRACKING; 1042 } 1043 return url; 1044 } 1045 1046 function render(){ 1047 btns.forEach(function(b){ 1048 b.setAttribute("aria-pressed", Number(b.dataset.amt) === sel ? "true" : "false"); 1049 }); 1050 freqWrap.querySelectorAll("button").forEach(function(b){ 1051 b.setAttribute("aria-pressed", b.dataset.freq === freq ? "true" : "false"); 1052 }); 1053 1054 var v = sel === 0 ? Math.round(Number(custom.value) || 0) : sel; 1055 1056 if (v > 0){ 1057 give.textContent = "Donate $" + v.toLocaleString() 1058 + (freq === "recurring" ? " recurring" : ""); 1059 } else { 1060 give.textContent = "Enter an amount"; 1061 } 1062 give.href = buildUrl(v > 0 ? v : 0); 1063 1064 if (note) note.textContent = NOTES[freq] || NOTES.onetime; 1065 } 1066 1067 btns.forEach(function(b){ 1068 b.addEventListener("click", function(){ 1069 sel = Number(b.dataset.amt); 1070 custom.style.display = sel === 0 ? "block" : "none"; 1071 if (sel === 0) custom.focus(); 1072 render(); 1073 }); 1074 }); 1075 1076 freqWrap.querySelectorAll("button").forEach(function(b){ 1077 b.addEventListener("click", function(){ 1078 freq = b.dataset.freq; 1079 render(); 1080 }); 1081 }); 1082 1083 custom.addEventListener("input", render); 1084 render(); 1085})(); 1086</script>
1086 1087<!--kg-card-end: html--> 1088<h3 id="what-we-investigate">What we investigate</h3><p>Our reporting follows the money, the influence and the decisions driving the climate crisis. We investigate topics including:</p><ul><li>Fossil fuel companies and the industries that support them</li><li>Corporate lobbying and political influence</li><li>AI, data centers and their growing environmental footprint</li><li>Government regulators and the agencies meant to hold polluters accountable</li><li>Communities living with the impacts of these decisions </li></ul><h3 id="how-we-partner">How we partner </h3><p>Floodlight was built on the idea that climate accountability reporting has the greatest impact when it's collaborative. We work with national partners like The Guardian, WIRED, PBS and NPR to produce investigations with national reach, then make every story available free of charge for local and regional newsrooms to republish, ensuring it reaches the communities most affected.</p><p>That model helps our journalism travel further than it could on its own. In 2025, Floodlight investigations were republished more than 1,000 times by over 500 newsrooms across the U.S. </p><p>If you're a news publisher, <a href="https://floodlightnews.org/republish-us/" rel="noreferrer">learn more</a> about reporting with us or sharing our work.</p><h3 id="where-to-find-us">Where to find us</h3><p>Floodlight stories don't just appear on our website and our partners' websites. Visual journalism is an increasingly important part of how we investigate and tell stories, such as our pioneering use of thermal drones to reveal the fossil fuel infrastructure powering AI data centres. </p><p>That's why video is a key part of our work. On Floodlightâs YouTube channel, you'll find our original short documentaries, which have helped our investigations reach millions of viewers.</p> 1089<!--kg-card-begin: html--> 1090<style> 1091.fl-vid{border-radius:4px; 1092 --fl-sky:#C1C8E1;--fl-navy:#1F2E5E;--fl-navy-deep:#17234A;--fl-beam:#FFF200; 1093 width:100%;box-sizing:border-box; 1094 background:var(--fl-navy); 1095 border-top:3px solid var(--fl-beam); 1096 padding:clamp(1.75rem,3.5vw,2.5rem) 0; 1097 font-family:inherit;color:#fff;overflow:hidden; 1098} 1099.fl-vid-head{display:flex;align-items:flex-end;justify-content:space-between;gap:1.5rem;padding:0 clamp(1.25rem,3vw,2rem);margin-bottom:1.25rem} 1100.fl-vid-eyebrow{margin:0 0 .5rem;font-size:1.05rem;font-weight:600;letter-spacing:.16em;text-transform:uppercase;color:var(--fl-beam)} 1101.fl-vid h2{margin:0;font-size:clamp(1.35rem,3.5vw,1.8rem);line-height:1.15;color:#fff} 1102.fl-vid-nav{display:flex;gap:.5rem;flex-shrink:0} 1103.fl-vid-nav button{width:2.4rem;height:2.4rem;display:grid;place-items:center;color:#fff;background:transparent;border:1px solid rgba(255,255,255,.28);border-radius:50%;cursor:pointer;font-size:1.1rem;line-height:1} 1104.fl-vid-nav button:hover:not(:disabled){background:rgba(255,255,255,.1);border-color:var(--fl-beam)} 1105.fl-vid-nav button:disabled{opacity:.3;cursor:default} 1106.fl-vid-nav button:focus-visible,.fl-card:focus-visible{outline:2px solid var(--fl-beam);outline-offset:2px} 1107.fl-track{display:flex;gap:.85rem;overflow-x:auto;scroll-snap-type:x mandatory;scroll-behavior:smooth;padding:0 clamp(1.25rem,3vw,2rem) .5rem;scrollbar-width:none;-ms-overflow-style:none} 1108.fl-track::-webkit-scrollbar{display:none} 1109.fl-card{flex:0 0 min(260px,72%);scroll-snap-align:start;text-decoration:none;color:#fff;display:block} 1110.fl-thumb{position:relative;aspect-ratio:16/9;background:var(--fl-navy-deep);border-radius:4px;overflow:hidden} 1111.fl-thumb img{width:100%;height:100%;object-fit:cover;display:block} 1112.fl-play{position:absolute;left:.7rem;bottom:.7rem;width:2.6rem;height:2.6rem;display:grid;place-items:center;background:var(--fl-beam);color:var(--fl-navy);border-radius:50%} 1113.fl-play svg{width:1.1rem;height:1.1rem;margin-left:2px} 1114.fl-card:hover .fl-play{background:#E6DA00} 1115.fl-card h3{margin:.75rem 0 .3rem;font-size:1rem;line-height:1.35;font-weight:600;color:#fff;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden} 1116.fl-card time{font-size:.85rem;color:rgba(255,255,255,.6)} 1117.fl-vid-msg{padding:0 clamp(1.25rem,3vw,2rem);font-size:.95rem;color:rgba(255,255,255,.7)} 1118.fl-vid-more{display:inline-block;margin:1.1rem clamp(1.25rem,3vw,2rem) 0;font-size:.95rem;font-weight:600;color:var(--fl-beam);text-decoration:none;border-bottom:1px solid rgba(255,242,0,.4);padding-bottom:2px} 1119.fl-vid-more:hover{color:var(--fl-beam);border-bottom-color:var(--fl-beam)} 1120.fl-skel{flex:0 0 min(260px,72%)} 1121.fl-skel div{aspect-ratio:16/9;background:rgba(255,255,255,.07);border-radius:4px} 1122@media (max-width:520px){.fl-vid-nav{display:none}} 1123</style> 1124 1125<section class="fl-vid" aria-labelledby="fl-vid-title"> 1126 1127 <div class="fl-vid-head"> 1128 <div> 1129 <p class="fl-vid-eyebrow">Watch</p> 1130 <h2 id="fl-vid-title">Our latest video reporting</h2> 1131 </div> 1132 <div class="fl-vid-nav"> 1133 <button type="button" id="fl-prev" aria-label="Previous videos" aria-controls="fl-track">←</button> 1134 <button type="button" id="fl-next" aria-label="More videos" aria-controls="fl-track">→</button> 1135 </div> 1136 </div> 1137 1138 <div class="fl-track" id="fl-track" tabindex="0" role="region" aria-label="Recent videos, scrolls horizontally"> 1139 <div class="fl-skel"><div></div></div> 1140 <div class="fl-skel"><div></div></div> 1141 <div class="fl-skel"><div></div></div> 1142 <div class="fl-skel"><div></div></div> 1143 </div> 1144 1145 <a class="fl-vid-more" id="fl-channel-link" href="#">See the whole channel →</a> 1146 1147</section> 1148
1149<script> 1150(function(){ 1151 1152 /* ===================================================================== 1153 CONFIG â everything you need to change is in this block. 1154 ===================================================================== */ 1155 1156 /* 1. Your YouTube channel ID. Starts with "UC". 1157 Find it at youtube.com/account_advanced, or on your channel page 1158 view-source and search for "channelId". 1159 A handle like @floodlightnews will NOT work here. */ 1160 var CHANNEL_ID = "REPLACE_WITH_YOUR_CHANNEL_ID"; 1161 1162 /* 2. YouTube Data API v3 key. Leave as "" to skip the API entirely and 1163 use the FALLBACK_VIDEOS list below instead. 1164 1165 To get one: console.cloud.google.com -> new project -> enable 1166 "YouTube Data API v3" -> Credentials -> Create API key. 1167 IMPORTANT: restrict the key to your domain (Application 1168 restrictions -> Websites -> add floodlightnews.org/*) and restrict 1169 it to the YouTube Data API only. The key is visible in page source, 1170 so these two restrictions are what keep it from being abused. */ 1171 var API_KEY = ""; 1172 1173 /* 3. Fallback list. Used when API_KEY is empty, or if the API call 1174 fails. Just video IDs â the part after "v=" in a YouTube URL. 1175 Newest first. Thumbnails and links work without any API. 1176 Titles are optional; leave "" and the card shows the date only. */ 1177 var FALLBACK_VIDEOS = [ 1178 { id: "5p426fSlYH4", title: "" }, 1179 { id: "NmRACRJ8EtQ", title: "" }, 1180 { id: "KitUtjFllbg", title: "" }, 1181 { id: "2Uwe74-6C9M", title: "" }, 1182 { id: "pMRyXY9uiMg", title: "" }, 1183 { id: "XTTI0XIphg4", title: "" } 1184 ]; 1185 1186 /* 4. How many videos to show when using the API (max 50). */ 1187 var COUNT = 12; 1188 1189 /* ===================================================================== */ 1190 1191 var root = document.querySelector(".fl-vid"); 1192 if (!root || root.dataset.flReady) return; 1193 root.dataset.flReady = "1"; 1194 1195 var track = root.querySelector("#fl-track"); 1196 var prev = root.querySelector("#fl-prev"); 1197 var next = root.querySelector("#fl-next"); 1198 var channelLink = root.querySelector("#fl-channel-link"); 1199 1200 if (CHANNEL_ID.indexOf("UC") === 0) { 1201 channelLink.href = "https://www.youtube.com/channel/" + CHANNEL_ID + "/videos"; 1202 } else { 1203 channelLink.style.display = "none"; 1204 } 1205 1206 function esc(s){ 1207 return String(s == null ? "" : s).replace(/[&<>"']/g, function(c){ 1208 return { "&":"&", "<":"<", ">":">", '"':""", "'":"'" }[c]; 1209 }); 1210 } 1211 1212 function fmtDate(iso){ 1213 if (!iso) return ""; 1214 var d = new Date(iso); 1215 if (isNaN(d)) return ""; 1216 return d.toLocaleDateString(undefined, { year:"numeric", month:"short", day:"numeric" }); 1217 } 1218 1219 function cardHtml(v){ 1220 var thumb = v.thumb || "https://i.ytimg.com/vi/" + v.id + "/hqdefault.jpg"; 1221 var date = fmtDate(v.date); 1222 return '<a class="fl-card" href="https://www.youtube.com/watch?v=' + esc(v.id) + '"' 1223 + ' target="_blank" rel="noopener">' 1224 + '<div class="fl-thumb"><img src="' + esc(thumb) + '" alt="" loading="lazy">' 1225 + '<span class="fl-play" aria-hidden="true"><svg viewBox="0 0 24 24" fill="currentColor">' 1226 + '<path d="M8 5v14l11-7z"></path></svg></span></div>' 1227 + (v.title ? '<h3>' + esc(v.title) + '</h3>' : '<h3>Watch on YouTube</h3>') 1228 + (date ? '<time datetime="' + esc(v.date) + '">' + esc(date) + '</time>' : '') 1229 + '</a>'; 1230 } 1231 1232 function paint(list){ 1233 list = list.filter(function(v){ return v && v.id; }); 1234 if (!list.length) { 1235 track.outerHTML = '<p class="fl-vid-msg">Videos are on their way. ' 1236 + 'Add a channel ID and API key, or a few video IDs, in the config block.</p>'; 1237 prev.parentNode.style.display = "none"; 1238 return; 1239 } 1240 track.innerHTML = list.map(cardHtml).join(""); 1241 updateArrows(); 1242 } 1243 1244 function step(){ 1245 var card = track.querySelector(".fl-card, .fl-skel"); 1246 if (!card) return track.clientWidth; 1247 var gap = parseFloat(getComputedStyle(track).columnGap || getComputedStyle(track).gap) || 16; 1248 return Math.round(card.getBoundingClientRect().width + gap); 1249 } 1250 1251 function updateArrows(){ 1252 var max = track.scrollWidth - track.clientWidth - 2; 1253 prev.disabled = track.scrollLeft <= 2; 1254 next.disabled = track.scrollLeft >= max; 1255 } 1256 1257 prev.addEventListener("click", function(){ track.scrollBy({ left: -step() }); }); 1258 next.addEventListener("click", function(){ track.scrollBy({ left: step() }); }); 1259 track.addEventListener("scroll", updateArrows, { passive: true }); 1260 window.addEventListener("resize", updateArrows); 1261 1262 if (API_KEY && CHANNEL_ID.indexOf("UC") === 0) { 1263 /* The uploads playlist ID is the channel ID with UC swapped for UU. 1264 Reading a playlist costs 1 quota unit; the search endpoint costs 1265 100, so this keeps you far inside the free daily allowance. */ 1266 var playlist = "UU" + CHANNEL_ID.slice(2); 1267 var url = "https://www.googleapis.com/youtube/v3/playlistItems" 1268 + "?part=snippet&maxResults=" + COUNT 1269 + "&playlistId=" + encodeURIComponent(playlist) 1270 + "&key=" + encodeURIComponent(API_KEY); 1271 1272 fetch(url) 1273 .then(function(r){ return r.ok ? r.json() : Promise.reject(r.status); }) 1274 .then(function(data){ 1275 paint((data.items || []).map(function(it){ 1276 var s = it.snippet || {}; 1277 var t = s.thumbnails || {}; 1278 var best = t.maxres || t.standard || t.high || t.medium || t.default || {}; 1279 return { 1280 id: (s.resourceId || {}).videoId, 1281 title: s.title, 1282 date: s.publishedAt, 1283 thumb: best.url 1284 }; 1285 })); 1286 }) 1287 .catch(function(err){ 1288 console.warn("YouTube fetch failed, using fallback list:", err); 1289 paint(FALLBACK_VIDEOS); 1290 }); 1291 } else { 1292 paint(FALLBACK_VIDEOS); 1293 } 1294})(); 1295</script>
1295 1296<!--kg-card-end: html--> 1297<p>You can also follow us on X, Bluesky, Instagram and Facebook. As Google's AI answers reduce search traffic and social media algorithms become increasingly unpredictable, every follow helps our investigations reach more people â and helps ensure you never miss our work.</p> 1298<!--kg-card-begin: html--> 1299<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@3/dist/tabler-icons.min.css"> 1300 1301<style> 1302.fl-social{ 1303 --fl-navy:#1F2E5E;--fl-beam:#FFF200; 1304 width:100%;background:transparent;font-family:inherit; 1305 padding:1.5rem 0; 1306} 1307.fl-social-row{ 1308 display:flex;flex-wrap:wrap;align-items:center;justify-content:center; 1309 gap:.75rem;width:100%;margin:0;padding:0;list-style:none; 1310} 1311.fl-social-row li{display:flex} 1312.fl-social-row a{ 1313 display:flex;align-items:center;justify-content:center; 1314 width:clamp(3rem,7vw,3.75rem);aspect-ratio:1; 1315 color:var(--fl-navy);background:transparent; 1316 border:1px solid rgba(31,46,94,.35);border-radius:50%; 1317 text-decoration:none;font-size:clamp(1.5rem,3.4vw,1.8rem); 1318 transition:background .15s ease,color .15s ease,border-color .15s ease; 1319} 1320.fl-social-row i{display:flex;align-items:center;justify-content:center;width:1em;height:1em;line-height:1} 1321.fl-social-row i::before{display:block;line-height:1;font-size:inherit} 1322 1323/* Tabler draws these brand glyphs at three different ink sizes, which makes 1324 the taller ones look like they sit higher. Measured from the font: the four 1325 below are 834 units tall, Bluesky is 792, and X / Instagram / YouTube are 1326 750. These rules scale everything down to a matching 750 so all eight read 1327 the same size. Bluesky also sits 21 units low, hence the extra shift. 1328 1329 LinkedIn additionally renders about 8px high for reasons the font metrics 1330 don't explain, so it gets a translateY measured off a screenshot. If it 1331 ever looks low, reduce .214em; if still high, increase it. */ 1332.fl-social-row .ti-brand-linkedin::before{transform:translateY(.214em) scale(.899)} 1333.fl-social-row .ti-brand-facebook::before, 1334.fl-social-row .ti-brand-threads::before, 1335.fl-social-row .ti-brand-tiktok::before{transform:scale(.899)} 1336.fl-social-row .ti-brand-bluesky::before{transform:translateY(-.021em) scale(.947)} 1337.fl-social-row a:hover{background:var(--fl-navy);color:var(--fl-beam);border-color:var(--fl-navy)} 1338.fl-social-row a:focus-visible{outline:2px solid var(--fl-navy);outline-offset:2px} 1339@media (prefers-reduced-motion:reduce){.fl-social-row a{transition:none}} 1340</style> 1341 1342<div class="fl-social"> 1343 1344 <ul class="fl-social-row"> 1345 1346 <!-- PASTE YOUR PROFILE URLS BELOW. 1347 Replace only the text inside each href="..." quote. 1348 Delete any whole <li> block for a platform you don't use. --> 1349 1350 <li><a href="https://www.linkedin.com/company/floodlight-news?ref=floodlightnews.org" target="_blank" rel="noopener" aria-label="Floodlight on LinkedIn"> 1351 <i class="ti ti-brand-linkedin" aria-hidden="true"></i></a></li> 1352 1353 <li><a href="https://www.facebook.com/floodlightnews.org" target="_blank" rel="noopener" aria-label="Floodlight on Facebook"> 1354 <i class="ti ti-brand-facebook" aria-hidden="true"></i></a></li> 1355 1356 <li><a href="https://www.threads.com/@floodlightnews?ref=floodlightnews.org" target="_blank" rel="noopener" aria-label="Floodlight on Threads"> 1357 <i class="ti ti-brand-threads" aria-hidden="true"></i></a></li> 1358 1359 <li><a href="https://bsky.app/profile/floodlightnews.org?ref=floodlightnews.org" target="_blank" rel="noopener" aria-label="Floodlight on Bluesky"> 1360 <i class="ti ti-brand-bluesky" aria-hidden="true"></i></a></li> 1361 1362 <li><a href="https://x.com/FloodlightNews?ref=floodlightnews.org" target="_blank" rel="noopener" aria-label="Floodlight on X"> 1363 <i class="ti ti-brand-x" aria-hidden="true"></i></a></li> 1364 1365 <li><a href="https://www.instagram.com/floodlightnews/?hl=en&ref=floodlightnews.org" target="_blank" rel="noopener" aria-label="Floodlight on Instagram"> 1366 <i class="ti ti-brand-instagram" aria-hidden="true"></i></a></li> 1367 1368 <li><a href="https://www.youtube.com/@floodlightnews?ref=floodlightnews.org" target="_blank" rel="noopener" aria-label="Floodlight on YouTube"> 1369 <i class="ti ti-brand-youtube" aria-hidden="true"></i></a></li> 1370 1371 <li><a href="https://www.tiktok.com/@floodlightnews?lang=en-GB&ref=floodlightnews.org" target="_blank" rel="noopener" aria-label="Floodlight on TikTok"> 1372 <i class="ti ti-brand-tiktok" aria-hidden="true"></i></a></li> 1373 1374 </ul> 1375 1376</div> 1377<!--kg-card-end: html--> 1378<h3 id="our-impact">Our impact</h3><p>Floodlightâs model has shown how journalism can hold the powerful to account and expose the harms of climate change on vulnerable communities. Many of our stories have spurred regulatory and corporate change around the country. Floodlight is also a leader in the news industry. Our reporting regularly wins prestigious state and national awards.</p><p>Read more about our <a href="https://floodlightnews.org/impact/">impact</a>.</p><h3 id="read-our-latest-stories">Read our latest stories</h3><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://floodlightnews.org/new-jersey-dataones-illegal-still-running/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">New Jersey says DataOneâs 62 generators are illegal. Itâs letting them run for 45 more days.</div><div class="kg-bookmark-description">The stateâs $1 million fine comes less than a month after Floodlightâs visual investigation publicly exposed the Vineland siteâs generator fleet for the first time</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/icon/Logo-Avatar-Small-52213376-d8ca-4b91-b262-fe5f5b8e6b4b.png" alt=""><span class="kg-bookmark-author">Floodlight</span><span class="kg-bookmark-publisher">Evan Simon/Floodlight</span></div></div>
1378<div class="kg-bookmark-thumbnail"><img src="https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/thumbnail/SIMON_DJI_20260818123405_0028_V-9c0b278a-503e-4ff8-be03-49e58cdde120.jpg" alt="" onerror="this.style.display = 'none'"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://floodlightnews.org/toxic-air-monitoring-decline/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Americaâs air monitors are disappearing. So is your power to expose polluters.</div><div class="kg-bookmark-description">Across several states, a quiet chemical industry campaign is making it harder to hold polluters accountable.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/icon/Logo-Avatar-Small-d3e6667e-ed11-47b3-8299-b0666cb448bb.png" alt=""><span class="kg-bookmark-author">Floodlight</span><span class="kg-bookmark-publisher">Ames Alexander/Floodlight</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/thumbnail/Plume-of-black-smoke-6b33d4db-53d5-4cb7-88d7-195bfb5cf867.jpg" alt="" onerror="this.style.display = 'none'"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://floodlightnews.org/new-jersey-data-center-federal-violation/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">One of East Coastâs largest data centers accused of âviolating federal lawâ</div><div class="kg-bookmark-description">Thermal drone footage shows DataOne â the Microsoft-backed AI data center in Vineland â is powering its operations with dozens of unpermitted generators</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/icon/Logo-Avatar-Small-6b22d34b-20c9-4320-9d07-010a366be3fa.png" alt=""><span class="kg-bookmark-author">Floodlight</span><span class="kg-bookmark-publisher">Evan Simon/Floodlight</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/thumbnail/SIMON_DJI_20260818122630_0011_V.mov.00_00_10_03.Still001-1-5d20767c-0d91-454a-9fe0-427ed16a94be.jpg" alt="" onerror="this.style.display = 'none'"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://floodlightnews.org/louisiana-toxic-air-monitoring/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Louisiana communities are left in the dark on toxic air â and industry helped keep it that way</div><div class="kg-bookmark-description">The stateâs air monitors are often miles from its biggest polluters and residents have little way of knowing what theyâre breathing. Thatâs no accident.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/icon/Logo-Avatar-Small-61682fef-d973-4b10-be9c-7c07a92d32d1.png" alt=""><span class="kg-bookmark-author">Floodlight</span><span class="kg-bookmark-publisher">Ames Alexander/Floodlight</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/thumbnail/Peter-DeCarlo-with-mobile-monitoring-van-1-0e5ff56c-8254-4229-af64-07a6411833b4.jpg" alt="" onerror="this.style.display = 'none'"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://floodlightnews.org/ai-data-centers-texas-pollution-loophole/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">This permit, common for dry cleaners, is now being used to build AI power plants</div><div class="kg-bookmark-description">Texas regulators are allowing AI data centers across the state to start up thousands of new pollution sources with no public notice or environmental reviews.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/icon/Logo-Avatar-Small-7137b8e5-dddd-4c4f-8c58-eb916ee165e8.png" alt=""><span class="kg-bookmark-author">Floodlight</span><span class="kg-bookmark-publisher">Evan Simon/Floodlight</span></div></div>
1378<div class="kg-bookmark-thumbnail"><img src="https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/thumbnail/SIMON_OGsplit-1-f2492efc-4e4d-4bcc-880f-4e923f6d866b.jpg" alt="" onerror="this.style.display = 'none'"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://floodlightnews.org/jay-morris-meta-louisiana-project-land-sales/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Louisiana senator helped secure Metaâs largest data center. Then he sold the land beside it.</div><div class="kg-bookmark-description">State senator John âJayâ Morris helped bring Metaâs Hyperion project to Richland Parish. Hereâs what he did with his land â before and after Metaâs announcement.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/icon/Logo-Avatar-Small-8996954b-3991-432e-ab54-c5ba7d2d0ca5.png" alt=""><span class="kg-bookmark-author">Floodlight</span><span class="kg-bookmark-publisher">Garrett Hazelwood/Floodlight</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/thumbnail/Jay-Morris-SC-May-14-0dde3793-cf9a-4aa2-9dc6-55b8b59692cf.JPG" alt="" onerror="this.style.display = 'none'"></div></a></figure> 1379 </section> 1380 1381 </article> 1382</main> 1383 1384 1385 1386 <footer class="gh-footer gh-outer"> 1387 <div class="gh-footer-inner gh-inner"> 1388 1389 <div class="gh-footer-bar"> 1390 <span class="gh-footer-logo is-title"> 1391 <img src="https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/2024/03/Floodlight-Logo-2.png" alt="Floodlight"> 1392 </span> 1393 <nav class="gh-footer-menu"> 1394 <ul class="nav"> 1395 <li class="nav-terms-of-use"><a href="https://floodlightnews.org/terms-of-use/">Terms of Use</a></li> 1396 <li class="nav-privacy-policy"><a href="https://floodlightnews.org/floodlight-privacy-policy/">Privacy Policy</a></li> 1397 <li class="nav-republication-terms"><a href="https://floodlightnews.org/republication-terms/">Republication Terms</a></li> 1398 <li class="nav-financials"><a href="https://floodlightnews.org/financials/">Financials</a></li> 1399 <li class="nav-follow-us"><a href="https://floodlightnews.org/">Follow Us</a></li> 1400</ul> 1401 1402 </nav> 1403 <div class="gh-footer-copyright"> 1404 Powered by <a href="https://ghost.org/" target="_blank" rel="noopener">Ghost</a> 1405 </div> 1406 </div> 1407 1408 <section class="gh-footer-signup"> 1409 <h2 class="gh-footer-signup-header is-title"> 1410 Climate. Investigations. Collaboration. 1411 </h2> 1412 <p class="gh-footer-signup-subhead is-body"> 1413 Floodlight exposes the powers stalling climate action. We partner with other newsrooms and share our stories for free. 1414 </p> 1415 <form class="gh-form" data-members-form> 1416 <input class="gh-form-input" id="footer-email" type="email" placeholder="[email protected]" required data-members-email> 1417 <button class="gh-button" type="submit" aria-label="Subscribe"> 1418 <span><span>Subscribe</span> <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" viewBox="0 0 256 256"><path d="M224.49,136.49l-72,72a12,12,0,0,1-17-17L187,140H40a12,12,0,0,1,0-24H187L135.51,64.48a12,12,0,0,1,17-17l72,72A12,12,0,0,1,224.49,136.49Z"></path></svg></span> 1419 <svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24"> 1420 <g stroke-linecap="round" stroke-width="2" fill="currentColor" stroke="none" stroke-linejoin="round" class="nc-icon-wrapper"> 1421 <g class="nc-loop-dots-4-24-icon-o"> 1422 <circle cx="4" cy="12" r="3"></circle> 1423 <circle cx="12" cy="12" r="3"></circle> 1424 <circle cx="20" cy="12" r="3"></circle> 1425 </g> 1426 <style data-cap="butt"> 1427 .nc-loop-dots-4-24-icon-o{--animation-duration:0.8s} 1428 .nc-loop-dots-4-24-icon-o *{opacity:.4;transform:scale(.75);animation:nc-loop-dots-4-anim var(--animation-duration) infinite} 1429 .nc-loop-dots-4-24-icon-o :nth-child(1){transform-origin:4px 12px;animation-delay:-.3s;animation-delay:calc(var(--animation-duration)/-2.666)} 1430 .nc-loop-dots-4-24-icon-o :nth-child(2){transform-origin:12px 12px;animation-delay:-.15s;animation-delay:calc(var(--animation-duration)/-5.333)} 1431 .nc-loop-dots-4-24-icon-o :nth-child(3){transform-origin:20px 12px} 1432 @keyframes nc-loop-dots-4-anim{0%,100%{opacity:.4;transform:scale(.75)}50%{opacity:1;transform:scale(1)}} 1433 </style> 1434 </g> 1435</svg> <svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52"> 1436 <path class="checkmark__check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8"/> 1437 <style> 1438 .checkmark { 1439 width: 40px; 1440 height: 40px; 1441 display: block; 1442 stroke-width: 2.5; 1443 stroke: currentColor; 1444 stroke-miterlimit: 10; 1445 } 1446 1447 .checkmark__check { 1448 transform-origin: 50% 50%; 1449 stroke-dasharray: 48; 1450 stroke-dashoffset: 48; 1451 animation: stroke .3s cubic-bezier(0.650, 0.000, 0.450, 1.000) forwards; 1452 } 1453 1454 @keyframes stroke { 1455 100% { stroke-dashoffset: 0; } 1456 } 1457 </style> 1458</svg> </button> 1459</form> </section> 1460 1461 </div> 1462</footer> 1463</div> 1464 1465 <div class="pswp" tabindex="-1" role="dialog" aria-hidden="true"> 1466 <div class="pswp__bg"></div> 1467 1468 <div class="pswp__scroll-wrap"> 1469 <div class="pswp__container"> 1470 <div class="pswp__item"></div> 1471 <div class="pswp__item"></div> 1472 <div class="pswp__item"></div> 1473 </div> 1474 1475 <div class="pswp__ui pswp__ui--hidden"> 1476 <div class="pswp__top-bar"> 1477 <div class="pswp__counter"></div> 1478 1479 <button class="pswp__button pswp__button--close" title="Close (Esc)"></button> 1480 <button class="pswp__button pswp__button--share" title="Share"></button> 1481 <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button> 1482 <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button> 1483 1484 <div class="pswp__preloader"> 1485 <div class="pswp__preloader__icn"> 1486 <div class="pswp__preloader__cut"> 1487 <div class="pswp__preloader__donut"></div> 1488 </div> 1489 </div> 1490 </div> 1491 </div> 1492 1493 <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap"> 1494 <div class="pswp__share-tooltip"></div> 1495 </div> 1496 1497 <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)"></button> 1498 <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)"></button> 1499 1500 <div class="pswp__caption"> 1501 <div class="pswp__caption__center"></div> 1502 </div> 1503 </div> 1504 </div> 1505</div>
1506<script src="https://floodlightnews.org/assets/built/source.js?v=fO0FIOFT6UAWW5WA"></script>
1506 1507
1508<script> 1509/* Floodlight Analytics - v1.2.0 - Added VisitStarted for session-based funnel tracking */ 1510(function () { 1511 const DEBUG = false; 1512 1513 const props = window.plausibleProps || {}; 1514 const downloadSelector = "a[href*='/content/files/'], a[download], a[href$='.pdf'], a[href$='.csv'], a[href$='.xlsx'], a[href$='.zip'], a[href$='.docx'], a[href$='.pptx']"; 1515 1516 const targets = [ 1517 { 1518 selector: ".outpost-upgrade-box .outpost-cta-submit", 1519 event: "UpgradeClicked_outpost_article_cta_5394g4zm" 1520 }, 1521 { 1522 selector: ".outpost-subscribe-box-66dew193 .outpost-cta-submit", 1523 event: "NewsletterSignupClicked_outpost_article_cta_66dew193" 1524 }, 1525 { 1526 selector: ".gh-footer-signup .gh-button[type='submit']", 1527 event: "NewsletterSignupClicked_footer_cta" 1528 }, 1529 { 1530 selector: ".gh-cta-inner.gh-inner .gh-button[type='submit']", 1531 event: "NewsletterSignupClicked_inner_cta" 1532 }, 1533 { 1534 selector: "a[data-portal='signup']", 1535 event: "NewsletterSignupOpened_global_subscribe_button" 1536 }, 1537 { 1538 selector: ".outpost-classic-box-text .outpost-cta-submit", 1539 event: "DonateButtonClicked_end_of_article" 1540 }, 1541 { 1542 selector: ".nav-donate a", 1543 event: "DonateButtonClicked_header" 1544 }, 1545 { 1546 selector: downloadSelector, 1547 event: "FileDownload" 1548 } 1549 ]; 1550 1551 function trackEvent(e, eventName) { 1552 if (DEBUG) console.log("ð¥ Tracking:", eventName, props); 1553 plausible(eventName, { props }); 1554 1555 if (eventName === "DonateButtonClicked_end_of_article" || eventName === "DonateButtonClicked_header") { 1556 plausible("DonateClicked", { props }); 1557 } 1558 } 1559 1560 function bindClick(selector, eventName) { 1561 const els = document.querySelectorAll(selector); 1562 els.forEach(el => { 1563 if (el._hasTracking) return; 1564 el.addEventListener("click", (e) => trackEvent(e, eventName)); 1565 el._hasTracking = true; 1566 }); 1567 } 1568 1569 function setupFloatingPortalTracking(attempt = 0) { 1570 const iframe = document.querySelector("iframe.gh-portal-triggerbtn-iframe"); 1571 if (!iframe || attempt > 10) return; 1572 1573 try { 1574 const doc = iframe.contentDocument || iframe.contentWindow.document; 1575 const trigger = doc.querySelector("[data-testid='portal-trigger-button']"); 1576 if (trigger && !trigger._hasTracking) { 1577 trigger.addEventListener("click", () => trackEvent(null, "NewsletterSignupClicked_floating_cta")); 1578 trigger._hasTracking = true; 1579 } 1580 1581 const continueBtn = doc.querySelector("button.gh-portal-btn[type='submit']"); 1582 if (continueBtn && !continueBtn._hasTracking) { 1583 continueBtn.addEventListener("click", () => trackEvent(null, "NewsletterSignupClicked_floating_cta")); 1584 continueBtn._hasTracking = true; 1585 } else if (!continueBtn) { 1586 setTimeout(() => setupFloatingPortalTracking(attempt + 1), 300); 1587 } 1588 } catch (err) { 1589 if (DEBUG) console.warn("Floating CTA error:", err); 1590 setTimeout(() => setupFloatingPortalTracking(attempt + 1), 300); 1591 } 1592 } 1593 1594 function setupPortalSignupTracking(attempt = 0) { 1595 const iframe = document.querySelector("iframe[title='portal-popup']"); 1596 if (!iframe || attempt > 10) return; 1597 1598 try { 1599 const doc = iframe.contentDocument || iframe.contentWindow.document; 1600 const buttons = doc.querySelectorAll("button.gh-portal-btn[type='submit']"); 1601 if (buttons.length === 0) { 1602 setTimeout(() => setupPortalSignupTracking(attempt + 1), 300); 1603 return; 1604 } 1605 1606 buttons.forEach(button => { 1607 if (!button._hasTracking) { 1608 button.addEventListener("click", () => 1609 trackEvent(null, "NewsletterSignupClicked_global_subscribe_button") 1610 ); 1611 button._hasTracking = true; 1612 } 1613 }); 1614 } catch (err) { 1615 if (DEBUG) console.warn("Portal signup error:", err); 1616 setTimeout(() => setupPortalSignupTracking(attempt + 1), 300); 1617 } 1618 } 1619 1620 // ð¹ NEW: Track session-based VisitStarted 1621 function trackVisitStarted() { 1622 if (!sessionStorage.getItem('floodlight_visit_tracked')) { 1623 plausible('VisitStarted'); 1624 sessionStorage.setItem('floodlight_visit_tracked', 'true'); 1625 sessionStorage.setItem('floodlight_pageviews', '1'); // Start count at 1 for this visit 1626 } else { 1627 const viewed = parseInt(sessionStorage.getItem('floodlight_pageviews') || '1'); 1628 const updatedViewCount = viewed + 1; 1629 sessionStorage.setItem('floodlight_pageviews', updatedViewCount.toString()); 1630 if (updatedViewCount === 2) { 1631 plausible('Viewed2Pages'); 1632 } 1633 } 1634 } 1635 1636 // ð¹ UTM source-based arrival tracking 1637 function trackArrivalEvents() {
1638 const urlParams = new URLSearchParams(window.location.search); 1639 const utmSource = urlParams.get('utm_source'); 1640 1641 if (utmSource === 'im-a-reader-newsletter') { 1642 plausible('ReaderNewsletterArrived'); 1643 } 1644 1645 if (utmSource === 'im-a-news-publisher-newsletter') { 1646 plausible('PublisherNewsletterArrived'); 1647 } 1648 } 1649 1650 function initTracking() { 1651 if (DEBUG) console.log("ð¢ Tracking initialized"); 1652 1653 trackVisitStarted(); // ð NEW visit-level trigger 1654 trackArrivalEvents(); 1655 1656 targets.forEach(({ selector, event }) => bindClick(selector, event)); 1657 setupFloatingPortalTracking(); 1658 setupPortalSignupTracking(); 1659 } 1660 1661 window.AnalyticsDebug = { 1662 reload: initTracking, 1663 track: trackEvent, 1664 props: window.plausibleProps || {}, 1665 enableDebug: () => window.DEBUG = true 1666 }; 1667 1668 window.addEventListener("load", () => { 1669 setTimeout(initTracking, 500); 1670 }); 1671 1672 const observer = new MutationObserver(() => { 1673 if (DEBUG) console.log("ð DOM changed - dynamic elements detected"); 1674 initTracking(); 1675 }); 1676 1677 observer.observe(document.body, { childList: true, subtree: true }); 1678})(); 1679</script>
1679 1680 1681 1682<!-- =================================================================== 1683 NESTED NAVIGATION FOR GHOST (Floodlight / Source theme) 1684 Paste into Ghost admin: Settings -> Code injection -> Site footer 1685 1686 PRIMARY navigation (header) -> parents only: About, Projects, 1687 Republish Our Work, Newsletter, Watch 1688 SECONDARY navigation (footer) -> include "Follow Us" 1689 1690 Children for both live in the CHILDREN block below. 1691 =================================================================== --> 1692 1693<!-- [1 of 4] Icon font. Without this the icons have nothing to draw. --> 1694<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@3/dist/tabler-icons.min.css"> 1695 1696<style> 1697 /* [2 of 4] Icon sizing. Nudge vertical-align if it sits high or low. */ 1698 /* Icon size. Raise font-size to make them bigger; nudge 1699 vertical-align if they then sit high or low. */ 1700 .fl-nav-icon{ 1701 display:inline-block;margin-right:.4em; 1702 font-size:1.4em;line-height:1;vertical-align:-.18em; 1703 } 1704 1705 /* ---- Dropdown container ---- */ 1706 .fl-has-sub{position:relative} 1707 .fl-sub{ 1708 position:absolute;top:100%;left:0;z-index:9999; 1709 min-width:13rem;margin:0;padding:.4rem 0; 1710 list-style:none; 1711 background:var(--background-color,var(--color-bg,#fff)); 1712 border:1px solid var(--color-border,rgba(0,0,0,.1)); 1713 border-radius:4px; 1714 box-shadow:0 6px 20px rgba(0,0,0,.09); 1715 opacity:0;visibility:hidden;transform:translateY(-4px); 1716 transition:opacity .14s ease,transform .14s ease,visibility .14s; 1717 } 1718 .fl-has-sub.fl-open > .fl-sub{opacity:1;visibility:visible;transform:translateY(0)} 1719 1720 .fl-sub li{margin:0;padding:0;position:relative} 1721 .fl-sub a{ 1722 display:flex;align-items:center; 1723 padding:.6rem 1.1rem; 1724 text-decoration:none;white-space:nowrap; 1725 /* Inherit from the surrounding menu, so header dropdowns match the 1726 header type and footer dropdowns match the footer type. The <li> 1727 is the source, which sidesteps your theme's font-size:0 rule on 1728 certain .nav links. */ 1729 font-size:inherit; 1730 } 1731 .fl-sub a:hover,.fl-sub a:focus-visible{background:rgba(0,0,0,.05)} 1732 .fl-sub a:focus-visible{outline:2px solid currentColor;outline-offset:-2px} 1733 1734 /* Third level opens to the side */ 1735 .fl-sub .fl-sub{top:-.4rem;left:100%;margin-left:2px} 1736 1737 /* ---- Footer menu sits at the bottom of the page, so open UPWARD ---- */ 1738 .gh-footer .fl-sub, 1739 .gh-footer-menu .fl-sub{ 1740 top:auto;bottom:100%;margin-bottom:.4rem;transform:translateY(4px); 1741 } 1742 .gh-footer .fl-has-sub.fl-open > .fl-sub, 1743 .gh-footer-menu .fl-has-sub.fl-open > .fl-sub{transform:translateY(0)} 1744 /* Caret points up when closed in the footer, down when open */ 1745 .gh-footer-menu .fl-caret svg{transform:rotate(180deg)} 1746 .gh-footer-menu .fl-has-sub.fl-open > .fl-caret svg{transform:rotate(0deg)} 1747 1748 /* Icons inside a dropdown: fixed width keeps the labels aligned */ 1749 /* Icons inside a dropdown get their own, larger size. This is the one 1750 to change if the menu icons look small - raise font-size. */ 1751 .fl-sub .fl-nav-icon{ 1752 font-size:1.35em; 1753 width:1.3em; 1754 text-align:center; 1755 margin-right:.5em; 1756 vertical-align:-.18em; 1757 } 1758 1759 /* ---- Caret toggle button ---- */ 1760 .fl-caret{ 1761 display:inline-flex;align-items:center;justify-content:center; 1762 width:1.9rem;height:1.9rem;margin-left:.15rem;padding:0; 1763 background:none;border:0;cursor:pointer; 1764 color:currentColor;vertical-align:middle; 1765 } 1766 /* Arrow size. Raise the width/height together to make it bigger. */ 1767 .fl-caret svg{width:1.05rem;height:1.05rem;transition:transform .14s ease} 1768 .fl-has-sub.fl-open > .fl-caret svg{transform:rotate(180deg)} 1769 .fl-caret:focus-visible{outline:2px solid currentColor;outline-offset:2px;border-radius:2px} 1770 .fl-sub .fl-caret svg{transform:rotate(-90deg)} 1771 1772 /* ---- Mobile / drawer: stack instead of float ---- */ 1773 @media (max-width:900px){ 1774 .fl-sub, 1775 .gh-footer .fl-sub, 1776 .gh-footer-menu .fl-sub{ 1777 position:static;opacity:1;visibility:visible;transform:none; 1778 display:none;background:transparent;box-shadow:none;border:0; 1779 min-width:0;margin:0;padding:.2rem 0 .2rem 1rem; 1780 } 1781 .fl-has-sub.fl-open > .fl-sub{display:block} 1782 .fl-sub a{color:inherit;padding:.45rem 0} 1783 .fl-sub a:hover{background:transparent} 1784 .fl-sub .fl-sub{left:auto;margin-left:0} 1785 } 1786 1787 @media (prefers-reduced-motion:reduce){ 1788 .fl-sub,.fl-caret svg{transition:none} 1789 } 1790</style> 1791
1792<script> 1793(function(){ 1794 "use strict"; 1795 1796 /* Set to true to log what the script finds, in the browser console. */ 1797 var DEBUG = false; 1798 1799 /* ===================================================================== 1800 ICONS - an icon beside a nav label. Key must match the Ghost label. 1801 Icon names: tabler.io/icons 1802 ===================================================================== */ 1803 var ICONS = { 1804 /* Generic icon for the footer parent. Alternatives worth trying: 1805 ti-users, ti-at, ti-heart, ti-rss, ti-hand-click */ 1806 "Follow Us": "ti ti-share", 1807 "Watch": "ti ti-brand-youtube", 1808 "LinkedIn": "ti ti-brand-linkedin", 1809 "Facebook": "ti ti-brand-facebook", 1810 "Threads": "ti ti-brand-threads", 1811 "Bluesky": "ti ti-brand-bluesky", 1812 "X": "ti ti-brand-x", 1813 "Instagram": "ti ti-brand-instagram", 1814 "YouTube": "ti ti-brand-youtube", 1815 "TikTok": "ti ti-brand-tiktok" 1816 }; 1817 1818 /* ===================================================================== 1819 CHILDREN - the dropdown contents. 1820 1821 Each key is the EXACT label of a parent item in Ghost's navigation 1822 (case doesn't matter). Works for both the primary (header) and 1823 secondary (footer) menus. blank:true opens the link in a new tab. 1824 ===================================================================== */ 1825 var CHILDREN = { 1826 /* --- primary navigation --- */ 1827 "Projects": [ 1828 { label: "Data Centers", url: "/tag/data-centers/" }, 1829 { label: "Gulf Coast", url: "/tag/gulf-coast/" }, 1830 { label: "America's Vanishing Coastline", url: "/tag/leaving-home/" }, 1831 { label: "Oil Money in Universities", url: "/tag/fueling-knowledge/" }, 1832 { label: "Climate Disinformation", url: "/tag/fueling-disinformation/" }, 1833 { label: "Utility Dark Money", url: "/tag/polluting-democracy/" }, 1834 { label: "Utilities Unchecked", url: "/tag/power-play/" } 1835 ], 1836 "About": [ 1837 { label: "About Us", url: "/about-floodlight/" }, 1838 { label: "Mission & Model", url: "/about/" }, 1839 { label: "Impact", url: "/impact/" }, 1840 { label: "Team", url: "/team/" }, 1841 { label: "Jobs", url: "/jobs/" }, 1842 { label: "Updates", url: "/newsroom-updates/" }, 1843 { label: "Contact", url: "/contact/" } 1844 ], 1845 1846 /* --- secondary navigation (footer) --- */ 1847 "Follow Us": [ 1848 { label: "LinkedIn", url: "https://www.linkedin.com/company/floodlight-news", blank: true }, 1849 { label: "Facebook", url: "https://www.facebook.com/floodlightnews.org", blank: true }, 1850 { label: "Threads", url: "https://www.threads.com/@floodlightnews", blank: true }, 1851 { label: "Bluesky", url: "https://bsky.app/profile/floodlightnews.org", blank: true }, 1852 { label: "X", url: "https://x.com/FloodlightNews", blank: true }, 1853 { label: "Instagram", url: "https://www.instagram.com/floodlightnews/", blank: true }, 1854 { label: "YouTube", url: "https://www.youtube.com/@floodlightnews", blank: true }, 1855 { label: "TikTok", url: "https://www.tiktok.com/@floodlightnews", blank: true } 1856 ] 1857 }; 1858 1859 /* ===================================================================== 1860 CONFIG - menu wrappers, matched to this theme. 1861 ===================================================================== */ 1862 var NAV_SELECTORS = [ 1863 ".gh-navigation-menu .nav", /* header */ 1864 ".gh-footer-menu .nav", /* footer / secondary nav */ 1865 "nav .nav", 1866 ".nav" 1867 ]; 1868 1869 /* Width below which menus stack instead of floating. 1870 Keep this the same as the media query in the CSS above. */ 1871 var MOBILE_BP = 900; 1872 1873 /* ===================================================================== */ 1874 1875 var CARET = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" ' 1876 + 'stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" ' 1877 + 'aria-hidden="true"><polyline points="6 9 12 15 18 9"></polyline></svg>'; 1878 1879 var uid = 0; 1880 1881 function isMobile(){ 1882 return window.innerWidth <= MOBILE_BP; 1883 } 1884 1885 function key(text){ 1886 return String(text || "") 1887 .replace(/\u2019/g, "'") 1888 .replace(/\s+/g, " ") 1889 .trim() 1890 .toLowerCase(); 1891 } 1892 1893 var LOOKUP = {};
1894 Object.keys(CHILDREN).forEach(function(k){ LOOKUP[key(k)] = CHILDREN[k]; }); 1895 1896 /* [3 of 4] Read ICONS and insert glyphs. Only touches links inside a 1897 dropdown or a top-level item - never rewrites an existing nav link 1898 that your theme already styles with Font Awesome. */ 1899 var ICON_LOOKUP = {}; 1900 Object.keys(ICONS).forEach(function(k){ ICON_LOOKUP[key(k)] = ICONS[k]; }); 1901 1902 function addIcons(scope){ 1903 scope.querySelectorAll("a").forEach(function(link){ 1904 if (link.dataset.flIcon) return; 1905 var li = link.closest("li"); 1906 /* skip your existing Font Awesome social items in the footer */ 1907 if (li && /nav-(threads|twitter|linkedin|instagram|youtube)\b/.test(li.className) 1908 && !link.closest(".fl-sub")) return; 1909 var cls = ICON_LOOKUP[key(link.textContent)]; 1910 if (!cls) return; 1911 link.dataset.flIcon = "1"; 1912 var i = document.createElement("i"); 1913 i.className = "fl-nav-icon " + (cls.indexOf("ti ") === 0 ? cls : "ti " + cls); 1914 i.setAttribute("aria-hidden", "true"); 1915 link.insertBefore(i, link.firstChild); 1916 if (DEBUG) console.log("[fl-nav] icon added to:", link.textContent.trim()); 1917 }); 1918 } 1919 1920 function nest(list){ 1921 Array.prototype.slice.call(list.children).forEach(function(li){ 1922 if (li.tagName !== "LI" || li.dataset.flDone) return; 1923 1924 var link = li.querySelector(":scope > a"); 1925 if (!link) return; 1926 1927 var kids = LOOKUP[key(link.textContent)]; 1928 if (!kids || !kids.length) return; 1929 1930 li.dataset.flDone = "1"; 1931 if (DEBUG) console.log("[fl-nav] nesting under:", link.textContent.trim()); 1932 1933 var sub = document.createElement("ul"); 1934 sub.className = "fl-sub"; 1935 sub.id = "fl-sub-" + (++uid); 1936 1937 kids.forEach(function(kid){ 1938 var item = document.createElement("li"); 1939 var a = document.createElement("a"); 1940 a.href = kid.url; 1941 a.textContent = kid.label; 1942 if (kid.blank){ 1943 a.target = "_blank"; 1944 a.rel = "noopener"; 1945 } 1946 item.appendChild(a); 1947 sub.appendChild(item); 1948 }); 1949 1950 li.appendChild(sub); 1951 addToggle(li, sub); 1952 }); 1953 } 1954 1955 function addToggle(li, sub){ 1956 li.classList.add("fl-has-sub"); 1957 1958 var btn = document.createElement("button"); 1959 btn.type = "button"; 1960 btn.className = "fl-caret"; 1961 btn.innerHTML = CARET; 1962 btn.setAttribute("aria-expanded", "false"); 1963 btn.setAttribute("aria-controls", sub.id); 1964 1965 var link = li.querySelector(":scope > a"); 1966 btn.setAttribute("aria-label", "Show submenu for " + (link ? link.textContent.trim() : "this item")); 1967 if (link && link.nextSibling) li.insertBefore(btn, link.nextSibling); 1968 else li.insertBefore(btn, sub); 1969 1970 function open(state){ 1971 li.classList.toggle("fl-open", state); 1972 btn.setAttribute("aria-expanded", state ? "true" : "false"); 1973 } 1974 1975 btn.addEventListener("click", function(e){ 1976 e.preventDefault(); 1977 e.stopPropagation(); 1978 var willOpen = !li.classList.contains("fl-open"); 1979 closeSiblings(li); 1980 open(willOpen); 1981 }); 1982 1983 li.addEventListener("mouseenter", function(){ if (!isMobile()) open(true); }); 1984 li.addEventListener("mouseleave", function(){ if (!isMobile()) open(false); }); 1985 li.addEventListener("focusin", function(){ if (!isMobile()) open(true); }); 1986 li.addEventListener("focusout", function(){ 1987 if (!isMobile() && !li.contains(document.activeElement)) open(false); 1988 }); 1989 } 1990 1991 function closeSiblings(li){ 1992 var parent = li.parentNode; 1993 if (!parent) return; 1994 Array.prototype.forEach.call(parent.children, function(sib){ 1995 if (sib !== li && sib.classList && sib.classList.contains("fl-open")){ 1996 sib.classList.remove("fl-open"); 1997 var b = sib.querySelector(":scope > .fl-caret"); 1998 if (b) b.setAttribute("aria-expanded", "false"); 1999 } 2000 }); 2001 } 2002 2003 function closeAll(){ 2004 document.querySelectorAll(".fl-has-sub.fl-open").forEach(function(li){ 2005 li.classList.remove("fl-open"); 2006 var b = li.querySelector(":scope > .fl-caret"); 2007 if (b) b.setAttribute("aria-expanded", "false"); 2008 }); 2009 } 2010 2011 var observer = null; 2012
2013 function init(){ 2014 if (observer) observer.disconnect(); 2015 NAV_SELECTORS.forEach(function(sel){ 2016 document.querySelectorAll(sel).forEach(function(list){ 2017 nest(list); 2018 addIcons(list); /* [4 of 4] without this call, ICONS is ignored */ 2019 }); 2020 }); 2021 if (DEBUG) console.log("[fl-nav] dropdowns:", document.querySelectorAll(".fl-has-sub").length); 2022 if (observer) watch(); 2023 } 2024 2025 /* Watch the header for the mobile drawer being built. The footer is 2026 server-rendered and static, so it doesn't need watching. */ 2027 function watch(){ 2028 if (!observer) return; 2029 var scope = document.querySelector("#gh-navigation") || 2030 document.querySelector("header") || 2031 document.body; 2032 if (scope) observer.observe(scope, { childList: true, subtree: true }); 2033 } 2034 2035 function start(){ 2036 if (!document.body) return; 2037 if (window.MutationObserver){ 2038 var pending = null; 2039 observer = new MutationObserver(function(){ 2040 clearTimeout(pending); 2041 pending = setTimeout(init, 60); 2042 }); 2043 } 2044 init(); 2045 } 2046 2047 document.addEventListener("click", function(e){ 2048 if (!e.target.closest(".fl-has-sub")) closeAll(); 2049 }); 2050 2051 document.addEventListener("keydown", function(e){ 2052 if (e.key === "Escape") closeAll(); 2053 }); 2054 2055 if (document.readyState === "loading"){ 2056 document.addEventListener("DOMContentLoaded", start); 2057 } else { 2058 start(); 2059 } 2060})(); 2061</script>
2061 2062 2063<!-- =================================================================== 2064 FLOODLIGHT NEWSLETTER POPUP 2065 Paste into Ghost admin: Settings -> Code injection -> Site footer 2066 2067 Behaviour: 2068 - appears after a delay (SHOW_AFTER below) 2069 - signs people up via Ghost's own magic-link endpoint, fetching 2070 the required integrity token first (without it Ghost returns 2071 400 Bad Request) 2072 - hidden for 7 days once dismissed 2073 - never shown to people who are already signed in as members 2074 - submits straight to Ghost's magic-link endpoint, so it doesn't 2075 depend on Portal picking up an injected form 2076 2077 If you're also running an Outpost popup, turn that one off or 2078 you'll get two. 2079 =================================================================== --> 2080 2081<style> 2082 .flp-overlay{ 2083 --flp-navy:#1F2E5E; 2084 --flp-sky:#C1C8E1; 2085 --flp-beam:#FFF200; 2086 --flp-ink:#3A4670; 2087 2088 position:fixed; 2089 inset:0; 2090 z-index:99999; 2091 display:flex; 2092 align-items:center; 2093 justify-content:center; 2094 padding:1.5rem; 2095 background:rgba(20,24,28,.55); 2096 opacity:0; 2097 visibility:hidden; 2098 transition:opacity .25s ease, visibility .25s; 2099 font-family:inherit; 2100 } 2101 .flp-overlay.is-open{opacity:1;visibility:visible} 2102 2103 .flp-modal{ 2104 position:relative; 2105 width:100%; 2106 max-width:820px; 2107 background:var(--flp-sky); 2108 border-radius:8px; 2109 overflow:hidden; 2110 box-shadow:0 24px 60px rgba(0,0,0,.3); 2111 transform:translateY(12px) scale(.98); 2112 transition:transform .25s ease; 2113 } 2114 .flp-overlay.is-open .flp-modal{transform:none} 2115 2116 /* ---- left: copy and form ---- */ 2117 /* Copy on the left, form on the right. */ 2118 .flp-body{ 2119 display:flex; 2120 /* Bottom-aligned, so the button row lines up with the last line of 2121 the copy on the left. */ 2122 align-items:flex-end; 2123 gap:clamp(1.5rem,3vw,2.5rem); 2124 padding:clamp(2rem,4vw,3rem) clamp(1.75rem,4vw,3rem) clamp(1.75rem,3.5vw,2.5rem); 2125 } 2126 /* Even split - copy left, form right. */ 2127 .flp-copy{flex:1 1 50%;min-width:0} 2128 .flp-side{flex:1 1 50%;min-width:0} 2129 2130 /* Navy text in a yellow box - yellow type on periwinkle is only 2131 1.4:1, but navy on yellow is 11:1. */ 2132 .flp-eyebrow{ 2133 display:inline-block; 2134 margin:0 0 .9rem; 2135 padding:.3rem .65rem; 2136 font-size:.95rem; 2137 letter-spacing:.16em; 2138 text-transform:uppercase; 2139 font-weight:700; 2140 color:var(--flp-navy); 2141 background:var(--flp-beam); 2142 border-radius:3px; 2143 } 2144 .flp-title{ 2145 margin:0 0 .7rem; 2146 font-size:clamp(2.2rem,4.2vw,2.95rem); 2147 line-height:1.08; 2148 font-weight:700; 2149 color:var(--flp-navy); 2150 } 2151 .flp-text{ 2152 margin:0; 2153 font-size:1.5rem; 2154 line-height:1.5; 2155 color:var(--flp-ink); 2156 } 2157 2158 .flp-form{display:flex;flex-direction:column;align-items:stretch;gap:.6rem;margin:0} 2159 2160 /* Button and fine print side by side. */ 2161 .flp-row{display:flex;align-items:center;gap:.9rem;flex-wrap:wrap} 2162 .flp-input{ 2163 width:100%; 2164 box-sizing:border-box; 2165 padding:1.05rem 1.2rem; 2166 font:inherit; 2167 font-size:1.2rem; 2168 color:var(--flp-navy); 2169 background:#fff; 2170 border:1.5px solid rgba(31,46,94,.28); 2171 border-radius:5px; 2172 } 2173 .flp-input::placeholder{color:#7A83A0} 2174 .flp-input:focus-visible{outline:2px solid var(--flp-navy);outline-offset:1px;border-color:var(--flp-navy)} 2175 2176 .flp-submit{ 2177 /* Sized to its label, not the field above it. */ 2178 width:auto; 2179 padding:1.05rem 2.2rem; 2180 font:inherit; 2181 font-size:1.2rem; 2182 font-weight:700; 2183 color:#fff; 2184 background:var(--flp-navy); 2185 border:0; 2186 border-radius:5px; 2187 cursor:pointer; 2188 transition:background .15s ease, color .15s ease; 2189 } 2190 /* Yellow on hover, navy text - white on yellow would be unreadable. */ 2191 .flp-submit:hover{background:var(--flp-beam);color:var(--flp-navy)} 2192 .flp-submit:disabled{opacity:.65;cursor:default} 2193 .flp-submit:focus-visible{outline:2px solid var(--flp-navy);outline-offset:2px} 2194 2195 .flp-fine{flex:1 1 8rem;margin:0;font-size:.95rem;line-height:1.35;color:#4A5578} 2196 /* Looks like a link, but it's a button because it performs an action 2197 rather than navigating. */ 2198 .flp-optout{ 2199 display:inline;padding:0;border:0;background:none; 2200 font:inherit;font-size:inherit;color:var(--flp-navy); 2201 text-decoration:underline;text-underline-offset:2px;cursor:pointer; 2202 } 2203 .flp-optout:hover{color:#16224A} 2204 .flp-optout:focus-visible{outline:2px solid var(--flp-navy);outline-offset:2px} 2205 .flp-msg{margin:1rem 0 0;font-size:1.1rem;line-height:1.45;font-weight:600} 2206 .flp-msg--ok{color:var(--flp-navy)} 2207 .flp-msg--err{color:#8C2A15} 2208 2209 /* ---- right: illustration ---- */ 2210 /* Bottom strip. Same background colour as the modal, so the sky in 2211 the illustration dissolves into the panel with no visible edge. 2212 Change the height to show more or less of the artwork. */ 2213 .flp-art{ 2214 height:210px; 2215 background-color:var(--flp-sky); 2216 background-repeat:no-repeat; 2217 background-position:center bottom; 2218 /* Full width, cropped only from the top - and the top is sky, so 2219 the crop is invisible. "cover" would crop the sides and can cut 2220 the car off. */ 2221 background-size:100% auto; 2222 } 2223 2224 /* ---- close ---- */ 2225 .flp-close{ 2226 position:absolute; 2227 top:.85rem; 2228 right:.95rem; 2229 width:2.25rem; 2230 height:2.25rem; 2231 display:grid; 2232 place-items:center; 2233 background:rgba(31,46,94,.08); 2234 color:var(--flp-navy); 2235 border:0; 2236 border-radius:50%; 2237 cursor:pointer; 2238 font-size:1.25rem; 2239 line-height:1; 2240 z-index:2; 2241 } 2242 .flp-close:hover{background:rgba(31,46,94,.16)} 2243 .flp-close:focus-visible{outline:2px solid var(--flp-navy);outline-offset:2px} 2244 2245 @media (max-width:720px){ 2246 .flp-overlay{padding:1rem;align-items:flex-end} 2247 .flp-modal{max-width:520px} 2248 .flp-body{flex-direction:column;gap:1.25rem} 2249 .flp-side{flex:1 1 auto;width:100%} 2250 .flp-submit{flex:1 1 auto} 2251 .flp-art{height:140px} 2252 .flp-text{font-size:1.05rem} 2253 } 2254 2255 @media (prefers-reduced-motion:reduce){ 2256 .flp-overlay,.flp-modal,.flp-submit{transition:none} 2257 } 2258</style> 2259
2260<script> 2261(function(){ 2262 "use strict"; 2263 2264 /* Set to true to log what the script is doing, in the browser console. */ 2265 var DEBUG = false; 2266 2267 /* ===================================================================== 2268 CONFIG 2269 ===================================================================== */ 2270 2271 /* Seconds before it appears. */ 2272 var SHOW_AFTER = 8; 2273 2274 /* Days to stay hidden after someone dismisses it. */ 2275 var HIDE_FOR_DAYS = 7; 2276 2277 /* Copy */ 2278 var EYEBROW = "Newsletter"; 2279 var TITLE = "Who’s stalling climate action?"; 2280 var TEXT = "Follow the power and politics blocking solutions — new investigations in your inbox twice a month."; 2281 var BUTTON = "Subscribe"; 2282 var FINE = "Free. Twice a month. Unsubscribe anytime."; 2283 var SUCCESS = "Thanks! Check your inbox and click the link to confirm."; 2284 var FAILURE = "Sorry, something went wrong. Please try again."; 2285 2286 /* Illustration on the right. Its sky is the same blue as the panel, 2287 so it blends. Set to "" to drop the image panel entirely. */ 2288 var IMAGE = "https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/size/w1600/2026/08/Presentation1-1.png"; 2289 2290 /* Shows in Ghost admin so you can see which signups came from here. */ 2291 var SIGNUP_LABEL = "popup"; 2292 2293 /* A label applied to every signup regardless of which widget they 2294 used, so you can see the whole newsletter audience in one filter. 2295 Currently switched off - set to a label name to turn it back on. 2296 Don't prefix it with "#": that only means something for post tags, 2297 not member labels. */ 2298 var SHARED_LABEL = ""; 2299 2300 /* Hide the newsletter CTAs from anyone already signed in as a member, 2301 using the same rules as the opt-out link: the sticky banner keeps 2302 its Donate button, the article split widget keeps its donate half, 2303 and the ABOUT page split widget is left completely alone. */ 2304 var HIDE_FOR_MEMBERS = true; 2305 2306 /* Your theme's own signup forms (homepage CTA, footer) live in theme 2307 files we can't edit from here. When this is true, SHARED_LABEL is 2308 added to them at page load so every signup on the site ends up 2309 under one label regardless of which form was used. Currently off - 2310 needs SHARED_LABEL set to something as well as this set to true. */ 2311 var LABEL_THEME_FORMS = false; 2312 2313 /* Which forms to label. These are Ghost's own member forms. */ 2314 var THEME_FORM_SELECTOR = "form[data-members-form]"; 2315 2316 /* ---- "Already a subscriber?" opt-out ---------------------------- 2317 Adds a link beside the fine print. Clicking it permanently hides 2318 the popup AND every other newsletter CTA for that person, in that 2319 browser. Set OPT_OUT_TEXT to "" to remove the link. */ 2320 var OPT_OUT_TEXT = "Already a subscriber? Hide these."; 2321 2322 /* What gets hidden once they opt out. Add selectors as you add CTAs. */ 2323 var HIDE_SELECTORS = [ 2324 ".fl-news", /* the wide newsletter CTA, pasted by hand */ 2325 ".fla", /* the same block, added to every article */ 2326 ".gh-cta", /* theme's homepage CTA */ 2327 ".gh-footer-signup" /* theme's footer signup */ 2328]; 2329 2330 /* The split widget is half newsletter, half donate - so rather than 2331 hiding the whole thing, the newsletter side is removed and the 2332 donate side goes full width. 2333 2334 Deliberately scoped to the ARTICLE version only. The about-page 2335 version (.fl-split--about) is left alone, because that page is 2336 where people go to find out about Floodlight and sign up - hiding 2337 the newsletter half there would remove the point of the page. 2338 2339 Set to "" to stop touching split widgets altogether. */ 2340 var SPLIT_SELECTOR = ".fl-split--article"; 2341 2342 /* Elements where only the SIGNUP FORM should go, leaving the rest of 2343 the component intact. The sticky banner is the case in point: it 2344 carries the email form and the Donate button, and someone opting 2345 out of newsletter prompts hasn't opted out of being asked to give. 2346 2347 To hide the whole sticky bar instead, move ".fl-stickybar" up into 2348 HIDE_SELECTORS and delete ".fl-sb-form" from here. */ 2349 var FORM_ONLY_SELECTORS = [ 2350 ".fl-sb-form" /* email form inside the sticky banner */ 2351 ]; 2352 2353 /* Pages where it should never appear. Matched against the path. */ 2354 var EXCLUDE_PATHS = ["/donate", "/sign-up-for-our-newsletter", "/membership", "/welcome"]; 2355 2356 /* Ghost internal tag that suppresses the popup on a given post. 2357 Internal tags get the slug "hash-nopopup", and Ghost's body_class 2358 helper normally outputs that as a class on <body>. 2359 2360 If your theme doesn't emit it (run the check in the comment at the 2361 bottom of this file), use either fallback instead - both work on 2362 any theme, added in the POST's own code injection header: 2363 2364 <meta name="fl-nopopup"> 2365 or 2366 <body data-nopopup> ... not editable, so use the meta tag. */ 2367 var EXCLUDE_TAG_CLASS = "tag-hash-nopopup"; 2368 2369 /* ===================================================================== 2370 Nothing below here needs editing 2371 ===================================================================== */ 2372
2373 var KEY_DISMISSED = "fl_popup_dismissed_until"; 2374 var KEY_SUBSCRIBED = "fl_popup_subscribed"; 2375 var KEY_HIDE_ALL = "fl_hide_newsletter_ctas"; 2376 2377 /* Add the shared label to any Ghost member form that doesn't already 2378 have it, so theme-rendered forms are tracked the same as ours. */ 2379 function labelThemeForms(){ 2380 if (!LABEL_THEME_FORMS || !SHARED_LABEL) return; 2381 document.querySelectorAll(THEME_FORM_SELECTOR).forEach(function(form){ 2382 if (form.dataset.flLabelled) return; 2383 var already = Array.prototype.some.call( 2384 form.querySelectorAll("input[data-members-label]"), 2385 function(i){ return i.value === SHARED_LABEL; } 2386 ); 2387 form.dataset.flLabelled = "1"; 2388 if (already) return; 2389 var input = document.createElement("input"); 2390 input.type = "hidden"; 2391 input.setAttribute("data-members-label", ""); 2392 input.value = SHARED_LABEL; 2393 form.appendChild(input); 2394 if (DEBUG) console.log("[fl-popup] labelled a theme form"); 2395 }); 2396 } 2397 2398 function optedOut(){ 2399 try { return !!localStorage.getItem(KEY_HIDE_ALL); } 2400 catch (e) { return false; } 2401 } 2402 2403 /* Hide every newsletter CTA on the page. Runs on load for anyone who 2404 has opted out, and immediately when they click the link. */ 2405 function hideAllCtas(){ 2406 if (document.getElementById("fl-hide-ctas")) return; 2407 var css = HIDE_SELECTORS.join(",") + "{display:none !important}"; 2408 if (FORM_ONLY_SELECTORS.length){ 2409 css += FORM_ONLY_SELECTORS.join(",") + "{display:none !important}"; 2410 } 2411 if (SPLIT_SELECTOR){ 2412 css += SPLIT_SELECTOR + "{grid-template-columns:1fr !important}" 2413 + SPLIT_SELECTOR + " .fl-half--news{display:none !important}"; 2414 } 2415 var st = document.createElement("style"); 2416 st.id = "fl-hide-ctas"; 2417 st.textContent = css; 2418 document.head.appendChild(st); 2419 } 2420 2421 function excluded(){ 2422 var path = location.pathname.toLowerCase(); 2423 if (EXCLUDE_PATHS.some(function(p){ return path.indexOf(p) === 0; })) return true; 2424 2425 /* 1. Ghost internal tag, via the body class */ 2426 if (EXCLUDE_TAG_CLASS && document.body && 2427 document.body.classList.contains(EXCLUDE_TAG_CLASS)) return true; 2428 2429 /* 2. Manual override: <meta name="fl-nopopup"> in the post's own 2430 code injection. Works regardless of theme or tag handling. */ 2431 if (document.querySelector('meta[name="fl-nopopup"]')) return true; 2432 2433 /* 3. Manual override: any element carrying data-nopopup, so you can 2434 also switch it off from inside a post's HTML card. */ 2435 if (document.querySelector("[data-nopopup]")) return true; 2436 2437 return false; 2438 } 2439 2440 function suppressed(){ 2441 try { 2442 if (localStorage.getItem(KEY_HIDE_ALL)) return true; 2443 if (localStorage.getItem(KEY_SUBSCRIBED)) return true; 2444 var until = Number(localStorage.getItem(KEY_DISMISSED) || 0); 2445 return until && Date.now() < until; 2446 } catch (e) { 2447 return false; /* private mode - just show it */ 2448 } 2449 } 2450 2451 function remember(key, days){ 2452 try { 2453 if (days) localStorage.setItem(key, String(Date.now() + days * 864e5)); 2454 else localStorage.setItem(key, "1"); 2455 } catch (e) {} 2456 } 2457 2458 var KEY_IS_MEMBER = "fl_is_member"; 2459 2460 /* Already signed in as a member? Portal's own endpoint answers this. 2461 The result is cached for the session, so on the second and later 2462 page views the CTAs are hidden immediately rather than after the 2463 network round trip - which avoids them flashing up first. */ 2464 function cachedMember(){ 2465 try { return sessionStorage.getItem(KEY_IS_MEMBER) === "1"; } 2466 catch (e) { return false; } 2467 } 2468 2469 function isMember(){ 2470 return fetch("/members/api/member/", { credentials: "include" }) 2471 .then(function(r){ return r.ok ? r.json() : null; }) 2472 .then(function(m){ 2473 var yes = !!(m && m.email); 2474 try { sessionStorage.setItem(KEY_IS_MEMBER, yes ? "1" : "0"); } catch (e) {} 2475 return yes; 2476 }) 2477 .catch(function(){ return false; }); 2478 } 2479 2480 function build(){ 2481 var overlay = document.createElement("div"); 2482 overlay.className = "flp-overlay"; 2483 overlay.setAttribute("role", "dialog"); 2484 overlay.setAttribute("aria-modal", "true"); 2485 overlay.setAttribute("aria-labelledby", "flp-title"); 2486 2487 overlay.innerHTML = 2488 '<div class="flp-modal">' + 2489 '<button type="button" class="flp-close" aria-label="Close">×</button>' + 2490 '<div class="flp-body">' + 2491 '<div class="flp-copy">' + 2492 '<p class="flp-eyebrow">' + EYEBROW + '</p>' + 2493 '<h2 class="flp-title" id="flp-title">' + TITLE + '</h2>' + 2494 '<p class="flp-text">' + TEXT + '</p>' + 2495 '</div>' + 2496 '<div class="flp-side">' + 2497 '<form class="flp-form" novalidate>' + 2498 '<label for="flp-email" style="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)">Email address</label>' + 2499 '<input class="flp-input" id="flp-email" type="email" required placeholder="[email protected]" autocomplete="email">' + 2500 '<div class="flp-row">' + 2501 '<button class="flp-submit" type="submit">' + BUTTON + '</button>' + 2502 '<p class="flp-fine">
2502' + FINE + 2503 (OPT_OUT_TEXT 2504 ? ' <button type="button" class="flp-optout">' + OPT_OUT_TEXT + '</button>' 2505 : '') + 2506 '</p>' + 2507 '</div>' + 2508 '</form>' + 2509 '<p class="flp-msg" hidden></p>' + 2510 '</div>' + 2511 '</div>' + 2512 (IMAGE ? '<div class="flp-art" style="background-image:url(\'' + IMAGE + '\')"></div>' : '') + 2513 '</div>'; 2514 2515 document.body.appendChild(overlay); 2516 return overlay; 2517 } 2518 2519 function init(){ 2520 var overlay = build(); 2521 var modal = overlay.querySelector(".flp-modal"); 2522 var closeEl = overlay.querySelector(".flp-close"); 2523 var form = overlay.querySelector(".flp-form"); 2524 var input = overlay.querySelector(".flp-input"); 2525 var submit = overlay.querySelector(".flp-submit"); 2526 var msg = overlay.querySelector(".flp-msg"); 2527 var lastFocus = null; 2528 2529 function open(){ 2530 lastFocus = document.activeElement; 2531 overlay.classList.add("is-open"); 2532 setTimeout(function(){ input.focus(); }, 60); 2533 } 2534 2535 function close(){ 2536 overlay.classList.remove("is-open"); 2537 remember(KEY_DISMISSED, HIDE_FOR_DAYS); 2538 if (lastFocus && lastFocus.focus) lastFocus.focus(); 2539 } 2540 2541 closeEl.addEventListener("click", close); 2542 2543 var optOut = overlay.querySelector(".flp-optout"); 2544 if (optOut){ 2545 optOut.addEventListener("click", function(){ 2546 remember(KEY_HIDE_ALL); 2547 hideAllCtas(); 2548 overlay.classList.remove("is-open"); 2549 if (lastFocus && lastFocus.focus) lastFocus.focus(); 2550 }); 2551 } 2552 2553 overlay.addEventListener("click", function(e){ 2554 if (e.target === overlay) close(); 2555 }); 2556 2557 document.addEventListener("keydown", function(e){ 2558 if (e.key === "Escape" && overlay.classList.contains("is-open")) close(); 2559 }); 2560 2561 /* Keep tabbing inside the modal while it's open */ 2562 modal.addEventListener("keydown", function(e){ 2563 if (e.key !== "Tab") return; 2564 var f = modal.querySelectorAll('button, input, a[href]'); 2565 if (!f.length) return; 2566 var first = f[0], last = f[f.length - 1]; 2567 if (e.shiftKey && document.activeElement === first){ e.preventDefault(); last.focus(); } 2568 else if (!e.shiftKey && document.activeElement === last){ e.preventDefault(); first.focus(); } 2569 }); 2570 2571 form.addEventListener("submit", function(e){ 2572 e.preventDefault(); 2573 var email = (input.value || "").trim(); 2574 if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)){ 2575 msg.hidden = false; 2576 msg.className = "flp-msg flp-msg--err"; 2577 msg.textContent = "Please enter a valid email address."; 2578 return; 2579 } 2580 2581 submit.disabled = true; 2582 submit.textContent = "Sending\u2026"; 2583 msg.hidden = true; 2584 2585 /* Only send labels that actually have a value, so an empty 2586 SHARED_LABEL doesn't get sent as a blank label. */ 2587 var labels = [SIGNUP_LABEL]; 2588 if (SHARED_LABEL) labels.push(SHARED_LABEL); 2589 2590 /* Ghost requires a short-lived integrity token on signup requests. 2591 Portal fetches one behind the scenes; without it the endpoint 2592 returns 400 Bad Request. So: get a token, then send. */ 2593 fetch("/members/api/integrity-token/", { credentials: "include" }) 2594 .then(function(r){ return r.ok ? r.text() : ""; }) 2595 .then(function(token){ 2596 return fetch("/members/api/send-magic-link/", { 2597 method: "POST", 2598 headers: { "Content-Type": "application/json" }, 2599 body: JSON.stringify({ 2600 email: email, 2601 emailType: "subscribe", 2602 labels: labels, 2603 name: "", 2604 autoRedirect: true, 2605 integrityToken: token 2606 }) 2607 }); 2608 }) 2609 .then(function(r){ 2610 if (!r.ok) { 2611 if (DEBUG) console.warn("[fl-popup] signup failed", r.status); 2612 throw new Error(r.status); 2613 } 2614 form.hidden = true; 2615 msg.hidden = false; 2616 msg.className = "flp-msg flp-msg--ok"; 2617 msg.textContent = SUCCESS; 2618 remember(KEY_SUBSCRIBED); 2619 setTimeout(function(){ overlay.classList.remove("is-open"); }, 4000); 2620 }) 2621 .catch(function(){ 2622 submit.disabled = false;
2623 submit.textContent = BUTTON; 2624 msg.hidden = false; 2625 msg.className = "flp-msg flp-msg--err"; 2626 msg.textContent = FAILURE; 2627 }); 2628 }); 2629 2630 setTimeout(open, SHOW_AFTER * 1000); 2631 } 2632 2633 function start(){ 2634 /* Runs everywhere, even on pages where the popup wouldn't show. */ 2635 labelThemeForms(); 2636 if (optedOut()) hideAllCtas(); 2637 2638 /* Hide straight away if we already know they're a member, then 2639 confirm with the API in case they've since signed out. */ 2640 if (HIDE_FOR_MEMBERS && cachedMember()) hideAllCtas(); 2641 2642 isMember().then(function(member){ 2643 if (member){ 2644 if (HIDE_FOR_MEMBERS) hideAllCtas(); 2645 return; /* members never see the popup */ 2646 } 2647 if (excluded() || suppressed()) return; 2648 init(); 2649 }); 2650 } 2651 2652 if (document.readyState === "loading"){ 2653 document.addEventListener("DOMContentLoaded", start); 2654 } else { 2655 start(); 2656 } 2657})(); 2658</script>
2658 2659 2660<!-- =================================================================== 2661 DOES THE #nopopup TAG WORK ON YOUR THEME? 2662 2663 Open a post tagged #nopopup and run this in the browser console: 2664 2665 console.log(document.body.className) 2666 2667 If you see "tag-hash-nopopup" in the list, the tag alone is enough - 2668 nothing more to do. 2669 2670 If you don't see it, Ghost isn't exposing internal tags on this 2671 theme. Use the meta tag instead: open the post in Ghost, go to 2672 Settings -> Code injection -> Post header, and paste: 2673 2674 <meta name="fl-nopopup"> 2675 2676 That suppresses the popup on that post only. 2677 =================================================================== --> 2678 2679<!-- =================================================================== 2680 THE "ALREADY A SUBSCRIBER?" OPT-OUT 2681 2682 Clicking it sets fl_hide_newsletter_ctas in localStorage, which 2683 hides the popup and everything listed in HIDE_SELECTORS, on every 2684 page, from then on. 2685 2686 Two things to know: 2687 - It's per browser and per device. Someone who opts out on their 2688 laptop will still see CTAs on their phone. There's no way round 2689 that without them signing in. 2690 - Clearing site data resets it. 2691 2692 To undo it while testing: 2693 localStorage.removeItem('fl_hide_newsletter_ctas'); location.reload(); 2694 2695 As you add new newsletter CTAs, add their selectors to 2696 HIDE_SELECTORS or they'll keep appearing for people who opted out. 2697 2698 The same selectors are used for signed-in members, so anything you 2699 add there is hidden from both groups. 2700 2701 TESTING AS A MEMBER 2702 sessionStorage.setItem('fl_is_member','1'); location.reload(); 2703 and to undo: 2704 sessionStorage.removeItem('fl_is_member'); location.reload(); 2705 =================================================================== --> 2706 2707 <!-- =================================================================== 2708 FLOODLIGHT AUTHOR SOCIAL ICONS 2709 Paste into Ghost admin: Settings -> Code injection -> Site footer 2710 2711 Ghost only has built-in fields for website, Facebook and X, so 2712 Bluesky, LinkedIn, Instagram and Threads can't be added in admin. 2713 This fills the theme's own (currently empty) .gh-author-social 2714 container instead. 2715 2716 TO ADD SOMEONE: add an entry to AUTHORS below, keyed by the slug in 2717 their author URL - /author/garrett/ is "garrett". Leave out or blank 2718 any platform they don't use. 2719 =================================================================== --> 2720 2721<style> 2722 .gh-author-social{ 2723 display:flex; 2724 flex-wrap:wrap; 2725 align-items:center; 2726 justify-content:center; 2727 gap:1.1rem; 2728 margin-top:1.1rem; 2729 } 2730 .fl-soc{ 2731 display:inline-flex; 2732 color:#1F2E5E; 2733 opacity:.75; 2734 text-decoration:none; 2735 transition:opacity .15s ease; 2736 } 2737 .fl-soc:hover{opacity:1;color:#1F2E5E} 2738 .fl-soc:focus-visible{outline:2px solid #1F2E5E;outline-offset:3px} 2739 .fl-soc svg{display:block} 2740 2741 @media (prefers-reduced-motion:reduce){ .fl-soc{transition:none} } 2742</style> 2743
2744<script> 2745(function(){ 2746 "use strict"; 2747 2748 var DEBUG = false; 2749 2750 /* ===================================================================== 2751 WHO HAS WHAT 2752 Keys are the slug from the author URL. Platforms supported: 2753 bluesky x linkedin instagram threads facebook website email 2754 Delete or blank anything they don't use. 2755 ===================================================================== */ 2756 2757 var AUTHORS = { 2758 garrett: { 2759 bluesky: "", 2760 x: "", 2761 linkedin: "https://www.linkedin.com/in/garretthazelwood", 2762 instagram: "https://www.instagram.com/garrett.hazelwood/", 2763 email: "[email protected]" 2764 }, 2765 ames: { 2766 bluesky: "https://bsky.app/profile/amesalex.bsky.social", 2767 x: "https://x.com/amesalex?lang=en", 2768 linkedin: "https://www.linkedin.com/in/ames-alexander-0778a75", 2769 facebook: "https://www.facebook.com/ames.alexander.1/", 2770 email: "[email protected]" 2771}, 2772 emily: { 2773 bluesky: "", 2774 x: "", 2775 linkedin: "", 2776 email: "[email protected]" 2777 }, 2778 brad: { 2779 bluesky: "", 2780 x: "", 2781 linkedin: "", 2782 email: "[email protected]" 2783 }, 2784 deneen: { 2785 linkedin: "", 2786 email: "[email protected]" 2787 }, 2788 evan: { 2789 bluesky: "", 2790 x: "", 2791 linkedin: "", 2792 email: "[email protected]" 2793 }, 2794 rosie: { 2795 bluesky: "", 2796 x: "", 2797 linkedin: "", 2798 email: "[email protected]" 2799 } 2800 }; 2801 2802 /* Order the icons appear in. */ 2803 var ORDER = ["bluesky","x","threads","instagram","linkedin","facebook","website","email"]; 2804 2805 /* ===================================================================== */ 2806 2807 var ICONS = { 2808 bluesky: '<path d="M6.335 5.144c-1.654 -1.199 -4.335 -2.127 -4.335 .826c0 .59 .35 4.953 .556 5.661c.713 2.463 3.13 2.75 5.444 2.369c-4.045 .665 -4.889 3.208 -2.667 5.41c1.03 1.018 1.913 1.59 2.667 1.59c2 0 3.134 -2.769 3.5 -3.5c.333 -.667 .5 -1.167 .5 -1.5c0 .333 .167 .833 .5 1.5c.366 .731 1.5 3.5 3.5 3.5c.754 0 1.637 -.571 2.667 -1.59c2.222 -2.203 1.378 -4.746 -2.667 -5.41c2.314 .38 4.73 .094 5.444 -2.369c.206 -.708 .556 -5.072 .556 -5.661c0 -2.953 -2.68 -2.025 -4.335 -.826c-2.293 1.662 -4.76 5.048 -5.665 6.856c-.905 -1.808 -3.372 -5.194 -5.665 -6.856z"></path>', 2809 x: '<path d="M4 4l11.733 16h4.267l-11.733 -16z"></path><path d="M4 20l6.768 -6.768m2.46 -2.46l6.772 -6.772"></path>', 2810 threads: '<path d="M19 7.5c-1.333 -3 -3.667 -4.5 -7 -4.5c-5 0 -7 3.5 -7 9s2 9 7 9c4 0 6 -2 6 -5c0 -3 -2 -4.5 -5 -4.5c-2 0 -3 1 -3 2.5c0 1.5 1 2.5 2.5 2.5c2 0 3.5 -1.5 3.5 -4c0 -2 -1 -3.5 -3 -3.5"></path>', 2811 instagram:'<rect x="4" y="4" width="16" height="16" rx="4"></rect><circle cx="12" cy="12" r="3"></circle><line x1="16.5" y1="7.5" x2="16.5" y2="7.501"></line>', 2812 linkedin: '<path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect x="2" y="9" width="4" height="12"></rect><circle cx="4" cy="4" r="2"></circle>', 2813 facebook: '<path d="M7 10v4h3v7h4v-7h3l1 -4h-4v-2a1 1 0 0 1 1 -1h3v-4h-3a5 5 0 0 0 -5 5v2h-3"></path>', 2814 website: '<circle cx="12" cy="12" r="9"></circle><line x1="3.6" y1="9" x2="20.4" y2="9"></line><line x1="3.6" y1="15" x2="20.4" y2="15"></line><path d="M11.5 3a17 17 0 0 0 0 18"></path><path d="M12.5 3a17 17 0 0 1 0 18"></path>', 2815 email: '<rect x="3" y="5" width="18" height="14" rx="2"></rect><path d="m3 7 9 6 9-6"></path>' 2816 }; 2817 2818 var LABELS = { 2819 bluesky:"Bluesky", x:"X", threads:"Threads", instagram:"Instagram", 2820 linkedin:"LinkedIn", facebook:"Facebook", website:"website", email:"Email" 2821 }; 2822 2823 /* Which domain each platform lives on, so we don't duplicate a link 2824 the theme already rendered from Ghost's own fields. */ 2825 var DOMAINS = { 2826 bluesky:"bsky.app", x:"x.com", threads:"threads.net", 2827 instagram:"instagram.com", linkedin:"linkedin.com", facebook:"facebook.com" 2828 }; 2829 2830 function slug(){ 2831 var m = (document.body.className || "").match(/author-([a-z0-9-]+)/g); 2832 if (!m) return null; 2833 for (var i = 0; i < m.length; i++){ 2834 var s = m[i].replace("author-", ""); 2835 if (s !== "template") return s; /* skip "author-template" */ 2836 } 2837 return null; 2838 } 2839 2840 function container(){ 2841 var el = document.querySelector(".gh-author-social"); 2842 if (el) return el; 2843 /* Fall back to building one if the theme ever stops rendering it. */ 2844 var meta = document.querySelector(".gh-author-meta") 2845 || document.querySelector(".gh-archive-wrapper"); 2846 if (!meta) return null; 2847 el = document.createElement("div"); 2848 el.className = "gh-author-social"; 2849 meta.appendChild(el); 2850 return el; 2851 } 2852 2853 function alreadyThere(box, key){ 2854 var d = DOMAINS[key]; 2855 if (!d) return false;
2856 return !!box.querySelector('a[href*="' + d + '"]'); 2857 } 2858 2859 function icon(key, url, name){ 2860 var href = key === "email" ? "mailto:" + url : url; 2861 var label = key === "email" 2862 ? "Email " + name 2863 : name + " on " + LABELS[key]; 2864 if (key === "website") label = name + "'s website"; 2865 2866 var a = document.createElement("a"); 2867 a.className = "fl-soc"; 2868 a.href = href; 2869 a.setAttribute("aria-label", label); 2870 if (key !== "email"){ 2871 a.target = "_blank"; 2872 a.rel = "noopener"; 2873 } 2874 a.innerHTML = 2875 '<svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 24 24"' 2876 + ' fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"' 2877 + ' stroke-linejoin="round" aria-hidden="true">' + ICONS[key] + '</svg>'; 2878 return a; 2879 } 2880 2881 function start(){ 2882 var s = slug(); 2883 if (!s) return; 2884 var person = AUTHORS[s]; 2885 if (!person){ 2886 if (DEBUG) console.log("[fl-author] no entry for", s); 2887 return; 2888 } 2889 2890 var box = container(); 2891 if (!box) return; 2892 2893 /* The page heading is "Name/Floodlight" - take the part before the 2894 slash for the aria-labels. */ 2895 var h = document.querySelector(".gh-article-title"); 2896 var name = h ? h.textContent.trim().split("/")[0].trim() : "this author"; 2897 2898 var added = 0; 2899 ORDER.forEach(function(key){ 2900 var url = person[key]; 2901 if (!url || !ICONS[key]) return; 2902 if (alreadyThere(box, key)) return; 2903 box.appendChild(icon(key, url, name)); 2904 added++; 2905 }); 2906 2907 if (DEBUG) console.log("[fl-author]", s, "- added", added, "icons"); 2908 } 2909 2910 if (document.readyState === "loading"){ 2911 document.addEventListener("DOMContentLoaded", start); 2912 } else { 2913 start(); 2914 } 2915})(); 2916</script>
2916 2917<!-- =================================================================== 2918 FLOODLIGHT NEWSLETTER CTA - added to every article automatically 2919 Navy directional wash over the stacks illustration. 2920 Paste into Ghost admin: Settings -> Code injection -> Site footer 2921 2922 Drops the wide newsletter block into every post after the sixth 2923 paragraph, so it doesn't have to be pasted in by hand. 2924 2925 Because the form is added by script rather than rendered by Ghost, 2926 Portal won't handle the signup - so this posts directly to Ghost's 2927 magic-link endpoint instead. 2928 2929 TO SKIP A POST: tag it #nocta, #readernewsletter or 2930 #publishernewsletter, or add <meta name="fl-nocta"> to that post's 2931 code injection header. 2932 =================================================================== --> 2933 2934<style> 2935 .fla{ 2936 --fla-navy:#1F2E5E; 2937 --fla-beam:#FFF200; 2938 2939 width:100vw; 2940 margin-left:calc(50% - 50vw); 2941 margin-right:calc(50% - 50vw); 2942 max-width:100vw; 2943 box-sizing:border-box; 2944 border-top:3px solid var(--fla-beam); 2945 font-family:inherit; 2946 color:#fff; 2947 2948 /* DIRECTIONAL WASH 2949 A flat wash dulls the artwork as much as it protects the text, 2950 even though only the left side has text on it. This gradient is 2951 heavy where the words are and nearly clear where the stacks are. 2952 2953 The stops, left to right: 2954 0-62% full strength navy - headline, lede, fine print 2955 62-80% fading out 2956 80-100% almost clear - the stacks at close to full strength 2957 2958 Move the 62% figure right if any text looks washed out, left if 2959 you want more of the artwork. */ 2960 background-color:#2A3A6B; 2961 background-image: 2962 linear-gradient(to right,
2963 rgba(31,46,94,.90) 0%, 2964 rgba(31,46,94,.90) 62%, 2965 rgba(31,46,94,.40) 80%, 2966 rgba(31,46,94,.08) 100%), 2967 url('https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/size/w1600/2026/08/stacks.png'); 2968 background-repeat:no-repeat; 2969 /* Anchored right and sized to the panel height, so the stacks sit 2970 whole rather than being cropped mid-chimney by "cover". */ 2971 background-position:right bottom; 2972 background-size:auto 100%; 2973 2974 padding:clamp(3rem,5.5vw,5rem) clamp(1.5rem,3.5vw,3rem); 2975 } 2976 2977 .fla a,.fla a:hover,.fla a:focus{text-decoration:none;box-shadow:none;border-bottom:0} 2978 2979 .fla-inner{ 2980 max-width:1100px; 2981 margin:0 auto; 2982 display:flex; 2983 align-items:flex-end; 2984 gap:clamp(1.5rem,4vw,3.5rem); 2985 padding-right:clamp(0px,12vw,220px); 2986 } 2987 .fla-copy{flex:1 1 50%;min-width:0} 2988 .fla-side{flex:1 1 50%;min-width:0} 2989 2990 .fla-eyebrow{ 2991 margin:0 0 1rem; 2992 font-size:1.05rem; 2993 font-weight:600; 2994 letter-spacing:.14em; 2995 text-transform:uppercase; 2996 color:var(--fla-beam); 2997 } 2998 .fla-title{ 2999 margin:0 0 .8rem; 3000 font-size:clamp(1.9rem,3.6vw,3.1rem); 3001 line-height:1.12; 3002 font-weight:700; 3003 color:#fff; 3004 } 3005 .fla-lede{ 3006 margin:0; 3007 font-size:clamp(1.2rem,1.7vw,1.5rem); 3008 line-height:1.5; 3009 color:rgba(255,255,255,.9); 3010 } 3011 3012 /* ---- form ---- */ 3013 .fla-form{margin:0} 3014 .fla-fields{display:flex;flex-wrap:wrap;gap:.5rem} 3015 .fla-input{ 3016 flex:1 1 14rem;min-width:0; 3017 padding:1.1rem 1.25rem; 3018 font:inherit;font-size:1.2rem; 3019 color:var(--fla-navy);background:#fff; 3020 border:1px solid rgba(255,255,255,.35); 3021 border-radius:4px; 3022 } 3023 .fla-input::placeholder{color:#7A83A0} 3024 .fla-input:focus-visible{outline:2px solid var(--fla-beam);outline-offset:1px} 3025 3026 .fla-button{ 3027 flex:0 0 auto; 3028 display:inline-flex;align-items:center;justify-content:center; 3029 min-width:10rem; 3030 padding:1.1rem 1.75rem; 3031 font:inherit;font-size:1.2rem;font-weight:600; 3032 color:#fff;background:var(--fla-navy); 3033 border:0;border-radius:4px;cursor:pointer; 3034 transition:background .15s ease,color .15s ease; 3035 } 3036 .fla-button:hover{background:var(--fla-beam);color:var(--fla-navy)} 3037 .fla-button:focus-visible{outline:2px solid var(--fla-beam);outline-offset:2px} 3038 .fla-button[disabled]{opacity:.7;cursor:default} 3039 3040 .fla-fine{ 3041 margin:1.2rem 0 0; 3042 font-size:1.2rem; 3043 line-height:1.5; 3044 color:rgba(255,255,255,.8); 3045 } 3046 3047 .fla-msg{margin:.9rem 0 0;font-size:1.05rem;line-height:1.5;font-weight:600} 3048 .fla-msg.is-ok{color:var(--fla-beam)} 3049 .fla-msg.is-error{color:#F5C4B3} 3050 3051 @media (max-width:800px){ 3052 /* Stacked layout: text runs the full width, so go back to a flat 3053 wash and let the artwork sit behind everything at low strength. */ 3054 .fla{ 3055 background-image: 3056 linear-gradient(rgba(31,46,94,.84), rgba(31,46,94,.84)), 3057 url('https://storage.ghost.io/c/95/96/9596ca6b-e09d-4cad-ac1c-03f11680f732/content/images/size/w1600/2026/08/stacks.png'); 3058 background-position:right bottom; 3059 background-size:cover; 3060 } 3061 .fla-inner{flex-direction:column;align-items:stretch;gap:1.5rem;padding-right:0} 3062 } 3063 @media (prefers-reduced-motion:reduce){ .fla-button{transition:none} } 3064</style> 3065
3066<script> 3067(function(){ 3068 "use strict"; 3069 3070 var DEBUG = false; 3071 3072 /* ===================================================================== 3073 WHERE IT APPEARS 3074 ===================================================================== */ 3075 3076 var ONLY_ON = "post-template"; /* articles only, not pages */ 3077 var CONTAINER = ".gh-content"; /* the article body */ 3078 3079 /* Which paragraph to sit after. Counts only top-level paragraphs, so 3080 headings, images and embeds are skipped rather than counted. Set to 3081 0 to append at the very end. */ 3082 var AFTER_PARAGRAPH = 6; 3083 3084 /* Posts carrying any of these tags never get it. */ 3085 var SKIP_TAG_CLASSES = [ 3086 "tag-hash-nocta", 3087 "tag-hash-readernewsletter", 3088 "tag-hash-publishernewsletter" 3089 ]; 3090 3091 var EXCLUDE_PATHS = ["/donate", "/membership", "/welcome"]; 3092 3093 /* ===================================================================== 3094 COPY AND TRACKING 3095 ===================================================================== */ 3096 3097 var EYEBROW = "Newsletter"; 3098 var TITLE = "The climate crisis has a backstory. We uncover it."; 3099 var LEDE = "Original investigations into how corporate influence and political decisions are delaying action \u2014 twice a month, straight to your inbox."; 3100 var BUTTON = "Subscribe"; 3101 var FINE = "No spam. Unsubscribe anytime."; 3102 var SUCCESS = "Email sent! Check your inbox to complete your signup."; 3103 var FAILURE = "Sorry, something went wrong. Please try again."; 3104 3105 var SIGNUP_LABEL = "newsletter-cta-wide-article"; 3106 3107 /* ===================================================================== */ 3108 3109 function excluded(){ 3110 var path = location.pathname.toLowerCase(); 3111 if (EXCLUDE_PATHS.some(function(p){ return path.indexOf(p) === 0; })) return true; 3112 if (SKIP_TAG_CLASSES.some(function(c){ return document.body.classList.contains(c); })) return true; 3113 if (document.querySelector('meta[name="fl-nocta"]')) return true; 3114 return false; 3115 } 3116 3117 function build(){ 3118 var wrap = document.createElement("div"); 3119 wrap.className = "fla"; 3120 wrap.innerHTML = 3121 '<div class="fla-inner">' + 3122 '<div class="fla-copy">' + 3123 '<p class="fla-eyebrow">' + EYEBROW + '</p>' + 3124 '<h2 class="fla-title">' + TITLE + '</h2>' + 3125 '<p class="fla-lede">' + LEDE + '</p>' + 3126 '</div>' + 3127 '<div class="fla-side">' + 3128 '<form class="fla-form" novalidate>' + 3129 '<div class="fla-fields">' + 3130 '<label for="fla-email" style="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)">Email address</label>' + 3131 '<input class="fla-input" id="fla-email" type="email" required placeholder="[email protected]" autocomplete="email">' + 3132 '<button class="fla-button" type="submit">' + BUTTON + '</button>' + 3133 '</div>' + 3134 '<p class="fla-msg" hidden></p>' + 3135 '</form>' + 3136 '<p class="fla-fine">' + FINE + '</p>' + 3137 '</div>' + 3138 '</div>'; 3139 return wrap; 3140 } 3141 3142 function wire(wrap){ 3143 var form = wrap.querySelector(".fla-form"); 3144 var input = wrap.querySelector(".fla-input"); 3145 var button = wrap.querySelector(".fla-button"); 3146 var msg = wrap.querySelector(".fla-msg"); 3147 var fields = wrap.querySelector(".fla-fields"); 3148 var fine = wrap.querySelector(".fla-fine"); 3149 3150 form.addEventListener("submit", function(e){ 3151 e.preventDefault(); 3152 var email = (input.value || "").trim(); 3153 if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)){ 3154 msg.hidden = false; 3155 msg.className = "fla-msg is-error"; 3156 msg.textContent = "Please enter a valid email address."; 3157 return; 3158 } 3159 3160 button.disabled = true; 3161 var label = button.textContent; 3162 button.textContent = "Sending\u2026"; 3163 msg.hidden = true; 3164 3165 /* Injected forms aren't picked up by Ghost's Portal, so this goes 3166 straight to the magic-link endpoint. Ghost needs an integrity 3167 token first or it returns 400. */ 3168 fetch("/members/api/integrity-token/", { credentials: "include" }) 3169 .then(function(r){ return r.ok ? r.text() : ""; }) 3170 .then(function(token){ 3171 return fetch("/members/api/send-magic-link/", { 3172 method: "POST", 3173 headers: { "Content-Type": "application/json" }, 3174 body: JSON.stringify({ 3175 email: email, 3176 emailType: "subscribe", 3177 labels: [SIGNUP_LABEL], 3178 name: "", 3179 autoRedirect: true, 3180 integrityToken: token 3181 }) 3182 }); 3183 }) 3184 .then(function(r){ 3185 if (!r.ok) throw new Error(r.status); 3186 fields.hidden = true; 3187 if (fine) fine.hidden = true; 3188 msg.hidden = false;
3189 msg.className = "fla-msg is-ok"; 3190 msg.textContent = SUCCESS; 3191 }) 3192 .catch(function(err){ 3193 if (DEBUG) console.warn("[fl-cta] signup failed", err); 3194 button.disabled = false; 3195 button.textContent = label; 3196 msg.hidden = false; 3197 msg.className = "fla-msg is-error"; 3198 msg.textContent = FAILURE; 3199 }); 3200 }); 3201 } 3202 3203 function place(box, wrap){ 3204 if (!AFTER_PARAGRAPH){ 3205 box.appendChild(wrap); 3206 return; 3207 } 3208 var paras = box.querySelectorAll(":scope > p"); 3209 if (!paras.length){ 3210 box.appendChild(wrap); 3211 return; 3212 } 3213 var index = Math.min(AFTER_PARAGRAPH, paras.length) - 1; 3214 var after = paras[index]; 3215 after.parentNode.insertBefore(wrap, after.nextSibling); 3216 if (DEBUG) console.log("[fl-cta] placed after paragraph", index + 1, "of", paras.length); 3217 } 3218 3219 function start(){ 3220 if (ONLY_ON && !document.body.classList.contains(ONLY_ON)) return; 3221 if (excluded()) return; 3222 3223 /* Don't double up with one pasted into the post by hand. */ 3224 if (document.querySelector(".fla, .fl-news")) return; 3225 3226 var box = document.querySelector(CONTAINER); 3227 if (!box){ 3228 if (DEBUG) console.warn("[fl-cta] container not found:", CONTAINER); 3229 return; 3230 } 3231 3232 var wrap = build(); 3233 place(box, wrap); 3234 wire(wrap); 3235 } 3236 3237 if (document.readyState === "loading"){ 3238 document.addEventListener("DOMContentLoaded", start); 3239 } else { 3240 start(); 3241 } 3242})(); 3243</script>
vendor: 1 bytes, line 3243
3243
3244<script> 3245!function(w,d,id,ns,s){var c=w[ns]=w[ns]||{};if(c.ready||(c.q=[],c.ready=function(){c.q.push(arguments)}),!d.getElementById(id)){var e=d.createElement(s);e.id=id,e.defer=true,e.src="https://assets.context.ly/kit/6.latest/loader.js";var h=d.getElementsByTagName(s)[0];h.parentNode.insertBefore(e,h)}}(window,document,"ctx-loader","Contextly","script"); 3246</script>
vendor: 1 bytes, line 3246
3246
3247<script> 3248Contextly.ready('widgets'); 3249</script>
vendor: 1 bytes, line 3249
3249
3250<script> 3251 const OutpostPub = { 3252 apiDomain: "https://floodlightghostio.outpost.pub", 3253 apiKey: "f2a7a6f5-2fc8-45ae-8f11-1c0231c72a02", 3254 siteName: "Floodlight", 3255 memberId: "", 3256 memberEmail: "", 3257 } 3258</script>
vendor: 1 bytes, line 3258
3258
3259<script defer src="https://assets.outpostpublishingcoop.com/assets/v2/js/outpost-pub.js"></script>
3259 3260 3261</body> 3262</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.