From e0106f4e2f8b3d6180bde1d58ca384d9b7812d50 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 26 Oct 2026 14:38:07 -0700 Subject: [PATCH 1/2] Webinar deck: credit the Nuffield Foundation on the closing slide On 22 October the Nuffield Foundation's communications team asked (via Anvar) for "funded" rather than "supported", and for its logo, the white version on a dark background. On 26 October Max agreed to add that credit to this deck's closing slide. - The closing slide shows "Funded by" and Nuffield's white logo under the contacts, linked to the Nuffield project page (sources.nuffield). - public/logos/funders/nuffield-foundation-white.svg is Nuffield's white logo as downloaded from nuffieldfoundation.org on 22 October (3,432 bytes, SHA-256 b3727385...654c), kept byte for byte. - Below 760px tall, the closing slide's title, QR card and contacts tighten a little, so "Thank you" stays clear of the PolicyEngine logo above it. - funder.test.ts pins the logo's hash, its size against content.ts and the "Funded by" label; ClosingSlide.test.tsx renders the slide and checks the credit and its link. - The README says where the logo came from and not to recolour or redraw it; the speaker notes say to thank Nuffield with "funded by". Co-Authored-By: Claude Opus 5.5 --- .../funders/nuffield-foundation-white.svg | 1 + slideshows/introduction-2026/README.md | 11 +++- slideshows/introduction-2026/SPEAKER-NOTES.md | 4 +- slideshows/introduction-2026/content.ts | 13 ++++ slideshows/introduction-2026/funder.test.ts | 31 ++++++++++ .../slides/ClosingSlide.test.tsx | 35 +++++++++++ .../introduction-2026/slides/deck.module.css | 59 +++++++++++++++++++ slideshows/introduction-2026/slides/index.tsx | 11 ++++ 8 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 public/logos/funders/nuffield-foundation-white.svg create mode 100644 slideshows/introduction-2026/funder.test.ts create mode 100644 slideshows/introduction-2026/slides/ClosingSlide.test.tsx diff --git a/public/logos/funders/nuffield-foundation-white.svg b/public/logos/funders/nuffield-foundation-white.svg new file mode 100644 index 0000000..a53f698 --- /dev/null +++ b/public/logos/funders/nuffield-foundation-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/slideshows/introduction-2026/README.md b/slideshows/introduction-2026/README.md index f2d846c..8dbc9ee 100644 --- a/slideshows/introduction-2026/README.md +++ b/slideshows/introduction-2026/README.md @@ -4,7 +4,8 @@ what PolicyEngine is, how we prepared for Budget day, and a slide with the live [Autumn Budget 2026 dashboard](https://ab2026-local-preview.vercel.app/uk/autumn-budget-2026) embedded in it, where the analysis is presented. The closing slide links the -same address, with its QR code. +same address, with its QR code, and credits the Nuffield Foundation, which +funds our UK Budget analysis. - `config.ts` controls the slide order and metadata. - `content.ts` holds the sources, the three pillars of What PolicyEngine is, @@ -21,3 +22,11 @@ same address, with its QR code. `public/images/introduction-2026/dashboard-qr.svg` encodes the dashboard's address. + +`public/logos/funders/nuffield-foundation-white.svg` is the Nuffield +Foundation's white logo, downloaded from nuffieldfoundation.org on 22 October +2026 (3,432 bytes, SHA-256 +`b3727385be9295f8daa3938c745f87b2e00e5675130354f6394ae6b3fec5654c`). That day +its communications team asked for "funded" rather than "supported", and for +its logo, the white version on a dark background. Keep the file as +downloaded; don't recolour or redraw it. diff --git a/slideshows/introduction-2026/SPEAKER-NOTES.md b/slideshows/introduction-2026/SPEAKER-NOTES.md index 343c303..db82a2e 100644 --- a/slideshows/introduction-2026/SPEAKER-NOTES.md +++ b/slideshows/introduction-2026/SPEAKER-NOTES.md @@ -65,4 +65,6 @@ that address. ## 5. Close -The dashboard link, with a QR code for phones, and how to reach us. +The dashboard link, with a QR code for phones, and how to reach us. Thank +the Nuffield Foundation, which funds our UK Budget analysis ("funded by", +not "supported by"). diff --git a/slideshows/introduction-2026/content.ts b/slideshows/introduction-2026/content.ts index ea68e52..de14782 100644 --- a/slideshows/introduction-2026/content.ts +++ b/slideshows/introduction-2026/content.ts @@ -56,6 +56,19 @@ export const sources = { export const about = "PolicyEngine is a nonprofit building free, open-source tools that help people understand tax and benefit policies."; +// The Nuffield Foundation's credit. On 22 October its communications team +// asked for "funded" rather than "supported", and for its logo, the white +// version on a dark background. The logo is the white one from +// nuffieldfoundation.org, kept as downloaded (3,432 bytes, SHA-256 +// b3727385…); its own size is 117.862 by 70. +export const funder = { + label: "Funded by", + name: "Nuffield Foundation", + logo: "/logos/funders/nuffield-foundation-white.svg", + width: 118, + height: 70, +}; + // What PolicyEngine is: the rules, the households and the results. The rules // are the main ones in the Good Growth Foundation deck's checked list. export const pillars = [ diff --git a/slideshows/introduction-2026/funder.test.ts b/slideshows/introduction-2026/funder.test.ts new file mode 100644 index 0000000..d927e27 --- /dev/null +++ b/slideshows/introduction-2026/funder.test.ts @@ -0,0 +1,31 @@ +import { createHash } from "node:crypto"; +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; +import { funder } from "./content"; + +// The white logo as downloaded from nuffieldfoundation.org on 22 October. Keep +// it byte for byte: don't recolour or redraw it. +const DOWNLOADED_SHA256 = + "b3727385be9295f8daa3938c745f87b2e00e5675130354f6394ae6b3fec5654c"; + +describe("the Nuffield Foundation credit on the closing slide", () => { + const file = path.join(__dirname, "../../public", funder.logo); + const bytes = readFileSync(file); + + it("uses the logo exactly as downloaded", () => { + expect(bytes.length).toBe(3432); + expect(createHash("sha256").update(bytes).digest("hex")).toBe(DOWNLOADED_SHA256); + }); + + it("matches the logo's own dimensions", () => { + const svg = bytes.toString("utf8"); + const size = (name: string) => Number(svg.match(new RegExp(`${name}="([\\d.]+)"`))?.[1]); + expect(Math.round(size("width"))).toBe(funder.width); + expect(Math.round(size("height"))).toBe(funder.height); + }); + + it('reads "Funded by"', () => { + expect(funder.label).toBe("Funded by"); + }); +}); diff --git a/slideshows/introduction-2026/slides/ClosingSlide.test.tsx b/slideshows/introduction-2026/slides/ClosingSlide.test.tsx new file mode 100644 index 0000000..a089e51 --- /dev/null +++ b/slideshows/introduction-2026/slides/ClosingSlide.test.tsx @@ -0,0 +1,35 @@ +import { act } from "react"; +import { createRoot, type Root } from "react-dom/client"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { funder, sources } from "../content"; +import { ClosingSlide } from "./index"; + +let container: HTMLDivElement; +let root: Root; + +beforeEach(async () => { + (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; + container = document.createElement("div"); + document.body.appendChild(container); + root = createRoot(container); + await act(async () => root.render()); +}); + +afterEach(() => { + act(() => root.unmount()); + container.remove(); +}); + +describe("the closing slide's funder credit", () => { + it('reads "Funded by" beside the Nuffield Foundation logo', () => { + const logo = container.querySelector(`img[alt="${funder.name}"]`); + expect(logo).not.toBeNull(); + expect(decodeURIComponent(logo!.getAttribute("src") ?? "")).toContain(funder.logo); + expect(logo!.closest("a")?.textContent).toContain("Funded by"); + }); + + it("links to the Nuffield Foundation's page for the project", () => { + const logo = container.querySelector(`img[alt="${funder.name}"]`); + expect(logo?.closest("a")?.getAttribute("href")).toBe(sources.nuffield.href); + }); +}); diff --git a/slideshows/introduction-2026/slides/deck.module.css b/slideshows/introduction-2026/slides/deck.module.css index ec0deb6..5df9931 100644 --- a/slideshows/introduction-2026/slides/deck.module.css +++ b/slideshows/introduction-2026/slides/deck.module.css @@ -466,6 +466,25 @@ color: var(--pe-teal); } +/* The funder's credit: its white logo as downloaded, on the slide's dark background. */ +.closingFunder { + display: flex; + align-items: center; + gap: 1.25rem; + color: inherit; +} + +.closingFunderLabel { + font-size: 1rem; + font-weight: 500; + opacity: 0.85; +} + +.closingFunderLogo { + width: auto; + height: 4.5rem; +} + /* Title slide: keep the logo, speakers and date clear of each other on laptops. */ @media (max-height: 760px) { .titleCover { @@ -592,6 +611,10 @@ width: 1.9rem; height: 1.9rem; } + + .closingFunderLogo { + height: 4rem; + } } /* Safari on a laptop leaves less than 760px for the slide. */ @@ -637,4 +660,40 @@ .prepText { font-size: 0.875rem; } + + /* The closing slide makes room for the funder's credit, so "Thank you" + stays clear of the PolicyEngine logo above it. */ + .closing { + gap: 1rem; + } + + .closingTitle { + font-size: 2.4rem; + } + + .closingCard { + padding: 0.75rem; + } + + .closingQr { + width: 6rem; + height: 6rem; + } + + .closingContact { + padding: 0.35rem 0.9rem; + } + + .closingContactIcon { + width: 1.7rem; + height: 1.7rem; + } + + .closingFunder { + gap: 1rem; + } + + .closingFunderLogo { + height: 3rem; + } } diff --git a/slideshows/introduction-2026/slides/index.tsx b/slideshows/introduction-2026/slides/index.tsx index a5f4019..0f2fd66 100644 --- a/slideshows/introduction-2026/slides/index.tsx +++ b/slideshows/introduction-2026/slides/index.tsx @@ -25,6 +25,7 @@ import SlideTitle from "@/components/layout/SlideTitle"; import { speakers } from "@/lib/speakers"; import { about, + funder, lastBudget, mockBudgets, pillars, @@ -302,6 +303,16 @@ export function ClosingSlide() { ))} + + {funder.label} + {funder.name} + ); From 7d207164c8a9eb33af5ed76d93d71c421e1ec9d6 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 28 Oct 2026 10:55:17 -0400 Subject: [PATCH 2/2] Webinar deck: serve it at /slides/autumn-budget-2026 The slug introduction-2026 dated from the 9 October introduction deck this grew out of. Rename the folder, the config and the QR image's folder to autumn-budget-2026, and redirect /slides/introduction-2026 to the new address. The page title drops "UK", as the cover did on 28 October. Co-Authored-By: Claude Opus 5.5 --- lib/slideshows.ts | 4 ++-- next.config.ts | 10 ++++++++++ .../dashboard-qr.svg | 0 .../README.md | 7 ++++--- .../SPEAKER-NOTES.md | 0 .../config.ts | 6 +++--- .../content.ts | 0 .../funder.test.ts | 0 .../slides/ClosingSlide.test.tsx | 0 .../slides/DashboardEmbed.tsx | 0 .../slides/deck.module.css | 0 .../slides/index.tsx | 2 +- slideshows/good-growth-foundation-2026/README.md | 2 +- 13 files changed, 21 insertions(+), 10 deletions(-) rename public/images/{introduction-2026 => autumn-budget-2026}/dashboard-qr.svg (100%) rename slideshows/{introduction-2026 => autumn-budget-2026}/README.md (87%) rename slideshows/{introduction-2026 => autumn-budget-2026}/SPEAKER-NOTES.md (100%) rename slideshows/{introduction-2026 => autumn-budget-2026}/config.ts (79%) rename slideshows/{introduction-2026 => autumn-budget-2026}/content.ts (100%) rename slideshows/{introduction-2026 => autumn-budget-2026}/funder.test.ts (100%) rename slideshows/{introduction-2026 => autumn-budget-2026}/slides/ClosingSlide.test.tsx (100%) rename slideshows/{introduction-2026 => autumn-budget-2026}/slides/DashboardEmbed.tsx (100%) rename slideshows/{introduction-2026 => autumn-budget-2026}/slides/deck.module.css (100%) rename slideshows/{introduction-2026 => autumn-budget-2026}/slides/index.tsx (99%) diff --git a/lib/slideshows.ts b/lib/slideshows.ts index dd175f8..dfea92a 100644 --- a/lib/slideshows.ts +++ b/lib/slideshows.ts @@ -29,10 +29,10 @@ import { overhang2026LightningConfig } from "@/slideshows/overhang-2026-lightnin import { blsTaxsim2026Config } from "@/slideshows/bls-taxsim-2026/config"; import { scottishGovernment2026Config } from "@/slideshows/scottish-government-2026/config"; import { goodGrowthFoundation2026Config } from "@/slideshows/good-growth-foundation-2026/config"; -import { introduction2026Config } from "@/slideshows/introduction-2026/config"; +import { autumnBudget2026Config } from "@/slideshows/autumn-budget-2026/config"; export const slideshows: SlideshowConfig[] = [ - introduction2026Config, + autumnBudget2026Config, goodGrowthFoundation2026Config, scottishGovernment2026Config, blsTaxsim2026Config, diff --git a/next.config.ts b/next.config.ts index c66c230..71f0735 100644 --- a/next.config.ts +++ b/next.config.ts @@ -8,6 +8,16 @@ const nextConfig: NextConfig = { env: { NEXT_PUBLIC_BASE_PATH: '/slides', }, + // The Autumn Budget 2026 webinar deck was introduction-2026 until 28 October. + async redirects() { + return [ + { + source: '/introduction-2026/:path*', + destination: '/autumn-budget-2026/:path*', + permanent: true, + }, + ]; + }, // Pin the workspace root so Turbopack does not silently pick up a stray // lockfile from a parent directory during local development or CI. turbopack: { diff --git a/public/images/introduction-2026/dashboard-qr.svg b/public/images/autumn-budget-2026/dashboard-qr.svg similarity index 100% rename from public/images/introduction-2026/dashboard-qr.svg rename to public/images/autumn-budget-2026/dashboard-qr.svg diff --git a/slideshows/introduction-2026/README.md b/slideshows/autumn-budget-2026/README.md similarity index 87% rename from slideshows/introduction-2026/README.md rename to slideshows/autumn-budget-2026/README.md index 8dbc9ee..d016199 100644 --- a/slideshows/introduction-2026/README.md +++ b/slideshows/autumn-budget-2026/README.md @@ -1,6 +1,7 @@ -# Analysing the UK Autumn Budget 2026 with PolicyEngine +# Analysing the Autumn Budget 2026 with PolicyEngine -5 slides at `/slides/introduction-2026`, for the 28 October 2026 webinar: +5 slides at `/slides/autumn-budget-2026`, for the 28 October 2026 webinar +(`/slides/introduction-2026` redirects here): what PolicyEngine is, how we prepared for Budget day, and a slide with the live [Autumn Budget 2026 dashboard](https://ab2026-local-preview.vercel.app/uk/autumn-budget-2026) embedded in it, where the analysis is presented. The closing slide links the @@ -20,7 +21,7 @@ funds our UK Budget analysis. here goes stale when the dashboard's data change. - `SPEAKER-NOTES.md` gives the presenter track. -`public/images/introduction-2026/dashboard-qr.svg` encodes +`public/images/autumn-budget-2026/dashboard-qr.svg` encodes the dashboard's address. `public/logos/funders/nuffield-foundation-white.svg` is the Nuffield diff --git a/slideshows/introduction-2026/SPEAKER-NOTES.md b/slideshows/autumn-budget-2026/SPEAKER-NOTES.md similarity index 100% rename from slideshows/introduction-2026/SPEAKER-NOTES.md rename to slideshows/autumn-budget-2026/SPEAKER-NOTES.md diff --git a/slideshows/introduction-2026/config.ts b/slideshows/autumn-budget-2026/config.ts similarity index 79% rename from slideshows/introduction-2026/config.ts rename to slideshows/autumn-budget-2026/config.ts index 5644fa2..d0f3e01 100644 --- a/slideshows/introduction-2026/config.ts +++ b/slideshows/autumn-budget-2026/config.ts @@ -7,9 +7,9 @@ import { WhatSlide, } from "./slides"; -export const introduction2026Config: SlideshowConfig = { - id: "introduction-2026", - title: "Analysing the UK Autumn Budget 2026 with PolicyEngine", +export const autumnBudget2026Config: SlideshowConfig = { + id: "autumn-budget-2026", + title: "Analysing the Autumn Budget 2026 with PolicyEngine", description: "What PolicyEngine is, how we prepared for Budget day, and the analysis, presented live on the dashboard.", date: "2026-10-28", diff --git a/slideshows/introduction-2026/content.ts b/slideshows/autumn-budget-2026/content.ts similarity index 100% rename from slideshows/introduction-2026/content.ts rename to slideshows/autumn-budget-2026/content.ts diff --git a/slideshows/introduction-2026/funder.test.ts b/slideshows/autumn-budget-2026/funder.test.ts similarity index 100% rename from slideshows/introduction-2026/funder.test.ts rename to slideshows/autumn-budget-2026/funder.test.ts diff --git a/slideshows/introduction-2026/slides/ClosingSlide.test.tsx b/slideshows/autumn-budget-2026/slides/ClosingSlide.test.tsx similarity index 100% rename from slideshows/introduction-2026/slides/ClosingSlide.test.tsx rename to slideshows/autumn-budget-2026/slides/ClosingSlide.test.tsx diff --git a/slideshows/introduction-2026/slides/DashboardEmbed.tsx b/slideshows/autumn-budget-2026/slides/DashboardEmbed.tsx similarity index 100% rename from slideshows/introduction-2026/slides/DashboardEmbed.tsx rename to slideshows/autumn-budget-2026/slides/DashboardEmbed.tsx diff --git a/slideshows/introduction-2026/slides/deck.module.css b/slideshows/autumn-budget-2026/slides/deck.module.css similarity index 100% rename from slideshows/introduction-2026/slides/deck.module.css rename to slideshows/autumn-budget-2026/slides/deck.module.css diff --git a/slideshows/introduction-2026/slides/index.tsx b/slideshows/autumn-budget-2026/slides/index.tsx similarity index 99% rename from slideshows/introduction-2026/slides/index.tsx rename to slideshows/autumn-budget-2026/slides/index.tsx index 0f2fd66..90d07fc 100644 --- a/slideshows/introduction-2026/slides/index.tsx +++ b/slideshows/autumn-budget-2026/slides/index.tsx @@ -278,7 +278,7 @@ export function ClosingSlide() {

Thank you