PageSourceSearch

https://floodlightnews.org/about-floodlight/

html floodlightnews.org collected 2026-10-02 16:40:17 UTC 139,250 bytes, 3,262 lines download raw bytes

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&#x27;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&#x27;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 { "&":"&amp;", "<":"&lt;", ">":"&gt;", '"':"&quot;", "'":"&#39;" }[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&hellip;</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.&nbsp;</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 &mdash; 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 &mdash; 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&nbsp;</li></ul><h3 id="how-we-partner">How we partner&nbsp;</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.&nbsp;</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.&nbsp;</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">&#8592;</button>
1134      <button type="button" id="fl-next" aria-label="More videos" aria-controls="fl-track">&#8594;</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 &#8594;</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 { "&":"&amp;", "<":"&lt;", ">":"&gt;", '"':"&quot;", "'":"&#39;" }[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&nbsp;<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&rsquo;s stalling climate action?";
2280  var TEXT    = "Follow the power and politics blocking solutions &mdash; 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">&times;</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.