diff --git a/src/components/InfoCallout.tsx b/src/components/InfoCallout.tsx new file mode 100644 index 00000000..d12d5317 --- /dev/null +++ b/src/components/InfoCallout.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import clsx from "clsx"; +import { Info } from "lucide-react"; + +type InfoCalloutProps = { + children: React.ReactNode; + className?: string; +}; + +/** + * A note or definition set apart from the surrounding prose: blue frame, pale + * blue fill, and a leading ⓘ. + * + * The resource pages have two tiers of box. Plain reference cards get a blue + * frame on white and no icon; this one adds the tint and the icon and is + * reserved for content the reader should treat as an aside — a caution, or a + * definition they may need before the surrounding text makes sense. Keeping + * the icon rare is the point: put it on every "allowed values" box and + * fourteen of them read as fourteen warnings. + * + * Callers supply their own heading, because the cases genuinely differ — a + * caution wants a bold lead-in inline with its prose, a definition wants a + * real `

`, and a footnote wants neither. + * + * This is deliberately not built on `AdditionalInfoBox`: that component wraps + * its content in a `
`, whose fixed margin would + * break the definition cards' grid alignment, and it is shared with the + * pathway detail page, which must not pick up this styling. + */ +const InfoCallout: React.FC = ({ children, className }) => ( +
+
+
+
+); + +export default InfoCallout; diff --git a/src/components/OnPageIndex.test.tsx b/src/components/OnPageIndex.test.tsx index 74be69a6..8e494fbc 100644 --- a/src/components/OnPageIndex.test.tsx +++ b/src/components/OnPageIndex.test.tsx @@ -4,7 +4,9 @@ import { render, screen, fireEvent, within, act } from "@testing-library/react"; import OnPageIndex from "./OnPageIndex"; import { MockIntersectionObserver } from "../test/mockIntersectionObserver"; -/** Harness: a container with three real headings, plus the index itself. */ +/** Harness: a container with three real headings, plus the index itself. + * The second section has sub-headings, so the nesting behavior can be + * exercised alongside the flat cases. */ const Harness: React.FC = () => { const containerRef = useRef(null); return ( @@ -13,6 +15,8 @@ const Harness: React.FC = () => {

First section

Second section

+

Second A

+

Second B

Third section

@@ -22,6 +26,9 @@ const Harness: React.FC = () => { const getActiveLink = () => screen.getAllByRole("link").find((link) => link.getAttribute("aria-current")); +const linkTexts = () => + screen.getAllByRole("link").map((link) => link.textContent); + describe("OnPageIndex", () => { // `location`/`history` persist across tests within this file (jsdom is // shared per test file, not per test), so start each test from a clean, @@ -161,3 +168,125 @@ describe("OnPageIndex", () => { expect(getActiveLink()?.textContent).toBe("First section"); }); }); + +describe("OnPageIndex — nested sub-headings (#955)", () => { + beforeEach(() => { + window.history.replaceState(null, "", "/"); + }); + + it("hides a section's sub-entries while another section is in view", () => { + render(); + // "First section" is active on load and has no sub-headings of its own. + expect(linkTexts()).not.toContain("Second A"); + expect(linkTexts()).not.toContain("Second B"); + }); + + it("reveals a section's sub-entries when it becomes the active one", () => { + render(); + const observer = MockIntersectionObserver.instances[0]; + + act(() => { + observer.trigger(document.getElementById("second")!, true); + }); + + expect(linkTexts()).toContain("Second A"); + expect(linkTexts()).toContain("Second B"); + }); + + it("hides them again once a different section takes over", () => { + render(); + const observer = MockIntersectionObserver.instances[0]; + + act(() => { + observer.trigger(document.getElementById("second")!, true); + }); + expect(linkTexts()).toContain("Second A"); + + act(() => { + observer.trigger(document.getElementById("second")!, false); + observer.trigger(document.getElementById("third")!, true); + }); + + expect(getActiveLink()?.textContent).toBe("Third section"); + expect(linkTexts()).not.toContain("Second A"); + }); + + it("keeps the parent section expanded when a sub-heading is the one in view", () => { + render(); + const observer = MockIntersectionObserver.instances[0]; + + act(() => { + observer.trigger(document.getElementById("second-b")!, true); + }); + + expect(getActiveLink()?.textContent).toBe("Second B"); + // Its sibling stays listed — the whole section's sub-entries are shown. + expect(linkTexts()).toContain("Second A"); + }); + + it("activates the topmost heading when several are inside the trigger band at once", () => { + render(); + const observer = MockIntersectionObserver.instances[0]; + + // Reported out of document order, as a real IntersectionObserver may. + act(() => { + observer.trigger(document.getElementById("second-b")!, true); + observer.trigger(document.getElementById("second")!, true); + observer.trigger(document.getElementById("second-a")!, true); + }); + + expect(getActiveLink()?.textContent).toBe("Second section"); + + // As the section scrolls past, the next heading down takes over. + act(() => { + observer.trigger(document.getElementById("second")!, false); + }); + expect(getActiveLink()?.textContent).toBe("Second A"); + }); + + it("keeps the last active entry when no heading is inside the band", () => { + render(); + const observer = MockIntersectionObserver.instances[0]; + + act(() => { + observer.trigger(document.getElementById("third")!, true); + }); + act(() => { + observer.trigger(document.getElementById("third")!, false); + }); + + expect(getActiveLink()?.textContent).toBe("Third section"); + }); + + it("scrolls to a sub-heading and updates the hash when its entry is clicked", () => { + render(); + const observer = MockIntersectionObserver.instances[0]; + act(() => { + observer.trigger(document.getElementById("second")!, true); + }); + + const scrollIntoView = vi.fn(); + document.getElementById("second-b")!.scrollIntoView = scrollIntoView; + const replaceState = vi.spyOn(window.history, "replaceState"); + + fireEvent.click(screen.getByRole("link", { name: "Second B" })); + + expect(scrollIntoView).toHaveBeenCalledWith({ + behavior: "smooth", + block: "start", + }); + expect(replaceState).toHaveBeenCalledWith(null, "", "#second-b"); + expect(getActiveLink()?.textContent).toBe("Second B"); + }); + + it("deep link: a URL pointing at a sub-heading lands on it and expands its parent", () => { + window.history.replaceState(null, "", "#second-a"); + const scrollIntoView = vi.spyOn(HTMLElement.prototype, "scrollIntoView"); + + render(); + + expect(getActiveLink()?.textContent).toBe("Second A"); + expect(scrollIntoView).toHaveBeenCalledWith({ block: "start" }); + expect(linkTexts()).toContain("Second B"); + }); +}); diff --git a/src/components/OnPageIndex.tsx b/src/components/OnPageIndex.tsx index c1b42c7e..a02fe7f7 100644 --- a/src/components/OnPageIndex.tsx +++ b/src/components/OnPageIndex.tsx @@ -1,53 +1,72 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; type IndexHeading = { id: string; label: string; }; +/** A top-level (`h2`) entry, together with the `h3` headings that follow it. */ +type IndexSection = IndexHeading & { + children: IndexHeading[]; +}; + type OnPageIndexProps = { /** Ref on the wrapper that contains this page's indexable content - * (everything except the hero). The index is built by scanning this - * subtree for `h2` elements that have an `id`, in DOM order — so the - * only thing a page needs to do to add/remove/reorder an entry is add - * or move an `id` on its own heading. */ + * (everything except the page header). The index is built by scanning + * this subtree for `h2` and `h3` elements that have an `id`, in DOM + * order — so the only thing a page needs to do to add/remove/reorder an + * entry is add or move an `id` on its own heading. `h3`s are nested + * under the `h2` that precedes them. */ containerRef: React.RefObject; }; /** * A sticky, left-side "on this page" index for long documentation pages. - * Highlights the section currently in view as the reader scrolls, and - * jumps to a section when its entry is clicked. + * + * Two levels deep: `h2`s are always listed, and the `h3`s belonging to the + * section currently in view are revealed underneath it. Sub-entries for + * every other section stay collapsed, so the index shows where you are + * without growing into a full outline of the page. */ const OnPageIndex: React.FC = ({ containerRef }) => { - const [headings, setHeadings] = useState([]); + const [sections, setSections] = useState([]); const [activeId, setActiveId] = useState(null); // Scan for indexable headings once, after the page's content has mounted. // None of the pages using this component conditionally render their - // top-level sections, so a single scan is enough. This must be a passive - // effect (not useLayoutEffect): OnPageIndex is rendered as an earlier - // sibling of the container it reads, and React only attaches a later - // sibling's ref once the earlier sibling's own layout effects have run — - // so containerRef.current would still be null at that point. Passive - // effects run only after the whole tree has committed, so the ref is - // guaranteed to be attached by the time this runs, regardless of DOM order. + // headings, so a single scan is enough. This must be a passive effect + // (not useLayoutEffect): OnPageIndex is rendered as an earlier sibling of + // the container it reads, and React only attaches a later sibling's ref + // once the earlier sibling's own layout effects have run — so + // containerRef.current would still be null at that point. Passive effects + // run only after the whole tree has committed, so the ref is guaranteed + // to be attached by the time this runs, regardless of DOM order. useEffect(() => { const container = containerRef.current; if (!container) return; const elements = Array.from( - container.querySelectorAll("h2[id]"), + container.querySelectorAll("h2[id], h3[id]"), ); - const found = elements.map((el) => ({ - id: el.id, - label: el.textContent?.trim() ?? "", - })); - setHeadings(found); + // Walk the headings in DOM order, attaching each `h3` to the `h2` that + // most recently preceded it. An `h3` before any `h2` has no parent to + // nest under and is skipped rather than promoted to the top level, so a + // stray sub-heading can't silently reorder the index. + const found: IndexSection[] = []; + elements.forEach((el) => { + const heading = { id: el.id, label: el.textContent?.trim() ?? "" }; + if (el.tagName === "H2") { + found.push({ ...heading, children: [] }); + } else { + found[found.length - 1]?.children.push(heading); + } + }); + + setSections(found); // Support deep links (e.g. shared as `.../methodology#classification- - // group-2`): if the URL already points at one of this page's sections, + // group-2`): if the URL already points at one of this page's headings, // land there instead of the first entry. This can't rely on the // browser's native "scroll to #fragment on load" behavior — that races // against React rendering the content in a client-rendered SPA and can @@ -62,9 +81,14 @@ const OnPageIndex: React.FC = ({ containerRef }) => { // decodeURIComponent throw. There's no error boundary in this app, // so an uncaught throw here would blank the entire page, not just // this component — fall back to the raw hash instead, which simply - // won't match any section id below. + // won't match any heading id below. } - const target = found.find((heading) => heading.id === targetId); + // A deep link may point at a sub-heading, not just a section, so match + // against both levels. Marking a sub-heading active also expands its + // parent section, since that's derived from the active id below. + const target = found + .flatMap((section) => [section, ...section.children]) + .find((heading) => heading.id === targetId); if (target) { document.getElementById(target.id)?.scrollIntoView({ block: "start" }); setActiveId(target.id); @@ -73,21 +97,50 @@ const OnPageIndex: React.FC = ({ containerRef }) => { } }, [containerRef]); + // Every indexed heading id, in DOM order, and the section each sub-heading + // belongs to. Memoized so the observer effect below doesn't tear down and + // rebuild on every render. + const { orderedIds, parentOf } = useMemo(() => { + const ids: string[] = []; + const parents = new Map(); + sections.forEach((section) => { + ids.push(section.id); + section.children.forEach((child) => { + ids.push(child.id); + parents.set(child.id, section.id); + }); + }); + return { orderedIds: ids, parentOf: parents }; + }, [sections]); + // Highlight the heading currently in view as the reader scrolls. useEffect(() => { - if (headings.length === 0) return; + if (orderedIds.length === 0) return; - const headingElements = headings - .map(({ id }) => document.getElementById(id)) + const headingElements = orderedIds + .map((id) => document.getElementById(id)) .filter((el): el is HTMLElement => el !== null); + // Several headings can sit inside the trigger band at once — much more + // so now that sub-headings are observed too, since an `h2` and the + // first `h3` under it are often only a paragraph apart. So track which + // headings are currently in the band and always pick the topmost of + // them, rather than whichever entry happened to be reported last. + const visible = new Set(); + const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { - setActiveId(entry.target.id); + visible.add(entry.target.id); + } else { + visible.delete(entry.target.id); } }); + const topmost = orderedIds.find((id) => visible.has(id)); + // When the band is empty (a long section scrolling past between two + // headings) keep the last active entry rather than clearing it. + if (topmost) setActiveId(topmost); }, // The top inset must stay below the landing offset headings scroll to // (`scroll-mt-8`/the sticky nav's `top-8`, 32px) — otherwise a heading @@ -107,7 +160,7 @@ const OnPageIndex: React.FC = ({ containerRef }) => { window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 2; if (atBottom) { - setActiveId(headings[headings.length - 1].id); + setActiveId(orderedIds[orderedIds.length - 1]); } }; window.addEventListener("scroll", handleScroll, { passive: true }); @@ -116,7 +169,7 @@ const OnPageIndex: React.FC = ({ containerRef }) => { observer.disconnect(); window.removeEventListener("scroll", handleScroll); }; - }, [headings]); + }, [orderedIds]); const handleClick = ( event: React.MouseEvent, @@ -140,7 +193,7 @@ const OnPageIndex: React.FC = ({ containerRef }) => { window.scrollTo({ top: 0, behavior: "smooth" }); // Matches the "first entry is bold on load" behavior: landing back at // the very top of the page should look the same as a fresh page load. - setActiveId(headings[0]?.id ?? null); + setActiveId(sections[0]?.id ?? null); // The URL no longer points at a specific section, so drop the hash. window.history.replaceState( null, @@ -149,11 +202,17 @@ const OnPageIndex: React.FC = ({ containerRef }) => { ); }; - // Deliberately never render `null` while `headings` is still empty: this + // Which section's sub-entries are on show: the active heading itself when + // it's a section, or the section owning the active sub-heading. + const expandedSectionId = activeId + ? (parentOf.get(activeId) ?? activeId) + : null; + + // Deliberately never render `null` while `sections` is still empty: this // component is the first (16rem) track of its parent grid, and briefly // rendering nothing would leave the content column as the grid's only // item — which places it in that same first track, squeezed to 16rem, - // until the scan effect above populates `headings` a moment later and it + // until the scan effect above populates `sections` a moment later and it // snaps back to full width. Always rendering the nav shell (its "Back to // top" link doesn't depend on the scan either) reserves the column from // the first paint and avoids that layout shift. @@ -162,7 +221,7 @@ const OnPageIndex: React.FC = ({ containerRef }) => { aria-label="On this page" className="hidden xl:block xl:sticky xl:top-8 self-start z-10" > -

+

On this page

    @@ -170,13 +229,14 @@ const OnPageIndex: React.FC = ({ containerRef }) => { Back to top - {headings.map(({ id, label }) => { + {sections.map(({ id, label, children }) => { const isActive = id === activeId; + const isExpanded = id === expandedSectionId; return ( // A bullet marker (not present on "Back to top" above) so a // section title that wraps onto a second line is still clearly @@ -193,11 +253,36 @@ const OnPageIndex: React.FC = ({ containerRef }) => { "block text-base leading-6 transition-colors " + (isActive ? "font-semibold text-rmiblue-800" - : "font-normal text-rmigray-600 hover:text-rmiblue-700") + : "font-normal text-rmigray-600 hover:text-rmiblue-800") } > {label} + + {isExpanded && children.length > 0 ? ( + + ) : null} ); })} diff --git a/src/components/PageHeader.tsx b/src/components/PageHeader.tsx new file mode 100644 index 00000000..f3e67bf5 --- /dev/null +++ b/src/components/PageHeader.tsx @@ -0,0 +1,61 @@ +import React from "react"; + +type PageHeaderProps = { + title: string; + /** One-line framing under the title. Omitted on the FAQ page. */ + subtitle?: string; + /** Intro prose for the page as a whole. Omitted on the Updates page. */ + children?: React.ReactNode; +}; + +/** + * The page header for the resource and contact pages. + * + * It is a full-bleed band — the background and the closing rule run to the + * viewport edges, with the copy inset by the usual `container` gutter, the + * same idiom as `Header` and `Footer`. That is what makes it read as a page + * header: the body below sits in a grid whose content column starts one + * 16rem index track further in, so a header that merely spanned the + * container looks like a section that failed to line up. Spanning the + * viewport instead makes the gutter alignment obviously the band's own. + * + * Render it as a **sibling of** the page's `container` div, never inside + * one, or it loses the full bleed. + * + * The copy is capped at `max-w-3xl` even though the band is not, which + * leaves visible slack to its right on wide screens. That is deliberate: + * `container` here is stock Tailwind, reaching 80rem at `xl` and 96rem at + * `2xl`, so uncapped intro paragraphs would run roughly 130 characters per + * line at 1440px and 180 on a wider monitor. The cap covers the title and + * subtitle too, rather than the prose alone, so all three share one left + * edge. Weighed against a two-column header and against dropping the cap; + * the reading measure won. + * + * Note this `
    ` renders inside App's `
    `, so it does not take + * `role="banner"` and cannot collide with `Header`, which does. + */ +const PageHeader: React.FC = ({ + title, + subtitle, + children, +}) => ( +
    +
    +
    +

    + {title} +

    + {subtitle ? ( +

    {subtitle}

    + ) : null} + {children ? ( +
    + {children} +
    + ) : null} +
    +
    +
    +); + +export default PageHeader; diff --git a/src/pages/ContactPage.test.tsx b/src/pages/ContactPage.test.tsx new file mode 100644 index 00000000..cfb83549 --- /dev/null +++ b/src/pages/ContactPage.test.tsx @@ -0,0 +1,42 @@ +import { describe, it, expect } from "vitest"; +import { render, screen, within } from "@testing-library/react"; +import { MemoryRouter } from "react-router"; +import ContactPage from "./ContactPage"; + +const renderPage = () => + render( + + + , + ); + +describe("ContactPage", () => { + it("lists its sections in the on-page index (#955)", () => { + renderPage(); + + const nav = screen.getByRole("navigation", { name: "On this page" }); + // First link is the "Back to top" entry, not one of the page's sections. + const links = within(nav).getAllByRole("link").slice(1); + + expect(links.map((link) => link.textContent)).toEqual([ + "How to reach us", + "Feedback and suggestions", + ]); + }); + + it("keeps the ways to get in touch", () => { + renderPage(); + + expect(screen.getByRole("link", { name: "Tom White" })).toHaveAttribute( + "href", + "mailto:tomwhite+tpr@rmi.org", + ); + expect(screen.getByRole("link", { name: "Nayra Herrera" })).toHaveAttribute( + "href", + "mailto:nherrera+tpr@rmi.org", + ); + expect( + screen.getByRole("link", { name: "GitHub repository" }), + ).toHaveAttribute("href", "https://github.com/RMI/tpr/issues"); + }); +}); diff --git a/src/pages/ContactPage.tsx b/src/pages/ContactPage.tsx index fce7b779..b24d79f5 100644 --- a/src/pages/ContactPage.tsx +++ b/src/pages/ContactPage.tsx @@ -1,100 +1,110 @@ -import React from "react"; +import React, { useRef } from "react"; +import OnPageIndex from "../components/OnPageIndex"; +import PageHeader from "../components/PageHeader"; const ContactPage: React.FC = () => { + const contentRef = useRef(null); + return (
    -
    -
    -
    -
    -
    + +

    + We are building the Transition Pathways Repository to support informed + transition-related decisions by making pathway data easier to access, + understand, and apply. +

    +

    + We actively engage with financial institutions, companies, + policymakers, and other stakeholders to refine the tool, expand its + coverage, and improve its usability. Whether you are using pathways in + analysis, strategy, risk management, or policy development, your input + helps shape how the repository evolves. +

    +
    -
    -

    - Contact -

    +
    +
    + -
    -

    - We are building the Transition Pathways Repository to support - informed transition-related decisions by making pathway data - easier to access, understand, and apply. +

    +
    +

    + How to reach us +

    +

    + We welcome feedback, questions, and opportunities for + collaboration.

    -

    - We actively engage with financial institutions, companies, - policymakers, and other stakeholders to refine the tool, expand - its coverage, and improve its usability. Whether you are using - pathways in analysis, strategy, risk management, or policy - development, your input helps shape how the repository evolves. +

    + To get in touch, reach out to{" "} + + Tom White + {" "} + or{" "} + + Nayra Herrera + + , or open an issue on our{" "} + + GitHub repository + {" "} + with a short description of your request.

    -
    -
    -
    +
-
-
-

- How to reach us -

-

- We welcome feedback, questions, and opportunities for - collaboration. -

-

- To get in touch, reach out to{" "} - - Tom White - {" "} - or{" "} - - Nayra Herrera - - , or open an issue on our{" "} - +

- GitHub repository - {" "} - with a short description of your request. -

-

- -
-

- Feedback and suggestions -

-

- We welcome feedback of all kinds and encourage you to share your - experience, questions, and ideas for improvement. -

-

- We are especially interested in hearing about: -

+ Feedback and suggestions +

+

+ We welcome feedback of all kinds and encourage you to share your + experience, questions, and ideas for improvement. +

+

+ We are especially interested in hearing about: +

- + + - + ); diff --git a/src/pages/resources/ResourcesFaqPage.test.tsx b/src/pages/resources/ResourcesFaqPage.test.tsx index c64ab609..512a1ce4 100644 --- a/src/pages/resources/ResourcesFaqPage.test.tsx +++ b/src/pages/resources/ResourcesFaqPage.test.tsx @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { render, screen } from "@testing-library/react"; +import { render, screen, within } from "@testing-library/react"; import { MemoryRouter } from "react-router"; import ResourcesFaqPage from "./ResourcesFaqPage"; @@ -16,25 +16,41 @@ const QUESTIONS = [ "What else is the TPR useful for?", ]; -describe("ResourcesFaqPage — single accordion (no section headers, no on-page index)", () => { - it("has no on-page index, since the page no longer has any section headings to list", () => { - render( - - - , - ); +const renderPage = () => + render( + + + , + ); +describe("ResourcesFaqPage — flat, always-expanded questions (#955)", () => { + it("renders all 10 questions as top-level headings, in order, with nothing collapsed", () => { + renderPage(); + + const headings = screen + .getAllByRole("heading", { level: 2 }) + .map((heading) => heading.textContent); + expect(headings).toEqual(QUESTIONS); + + // The accordion is gone: every answer is readable without a click. + expect(screen.queryAllByRole("button")).toHaveLength(0); expect( - screen.queryByRole("navigation", { name: "On this page" }), - ).not.toBeInTheDocument(); + screen.getByText(/The TPR centralizes and standardizes this information/), + ).toBeInTheDocument(); + }); + + it("lists every question in the on-page index", () => { + renderPage(); + + const nav = screen.getByRole("navigation", { name: "On this page" }); + // First link is the "Back to top" entry, not one of the questions. + const links = within(nav).getAllByRole("link").slice(1); + + expect(links.map((link) => link.textContent)).toEqual(QUESTIONS); }); it("no longer shows the 'About the TPR' / 'Using the TPR' / CTAs category headings", () => { - render( - - - , - ); + renderPage(); expect(screen.queryByText("About the TPR")).not.toBeInTheDocument(); expect(screen.queryByText("Using the TPR")).not.toBeInTheDocument(); @@ -44,24 +60,4 @@ describe("ResourcesFaqPage — single accordion (no section headers, no on-page ), ).not.toBeInTheDocument(); }); - - it("renders all 10 questions as one flat, collapsed-by-default accordion, in order", () => { - render( - - - , - ); - - const buttons = QUESTIONS.map((question) => - screen.getByRole("button", { name: question }), - ); - buttons.forEach((button) => { - expect(button).toHaveAttribute("aria-expanded", "false"); - }); - - // All 10 live in the same single list, in the same top-to-bottom order. - const allButtons = screen.getAllByRole("button"); - expect(allButtons).toHaveLength(QUESTIONS.length); - expect(allButtons).toEqual(buttons); - }); }); diff --git a/src/pages/resources/ResourcesFaqPage.tsx b/src/pages/resources/ResourcesFaqPage.tsx index 85cfe18c..8b83c1b8 100644 --- a/src/pages/resources/ResourcesFaqPage.tsx +++ b/src/pages/resources/ResourcesFaqPage.tsx @@ -1,31 +1,39 @@ -import React, { useMemo } from "react"; +import React, { useMemo, useRef } from "react"; import { Link } from "react-router"; -import AccordionItem from "../../components/AccordionItem"; +import OnPageIndex from "../../components/OnPageIndex"; +import PageHeader from "../../components/PageHeader"; type FaqItem = { + /** Slug used for the heading's `id`, so the on-page index can list the + * question and deep links to it stay stable if the wording is edited. */ + id: string; question: string; answer: React.ReactNode; }; -const FaqItemBlock: React.FC = ({ question, answer }) => ( - - {question} - - } - panelClassName="mt-5 border-t border-neutral-200 pt-5 text-rmigray-700" - > -
+/** Each question is a top-level section of the page: always expanded, and + * listed in the on-page index. */ +const FaqItemBlock: React.FC = ({ id, question, answer }) => ( +
+

+ {question} +

+
{answer}
- +
); const ResourcesFaqPage: React.FC = () => { + const contentRef = useRef(null); + const faqItems = useMemo( () => [ { + id: "what-is-the-tpr-for", question: "What is the Transition Pathways Repository (TPR) for?", answer: ( <> @@ -62,6 +70,7 @@ const ResourcesFaqPage: React.FC = () => { ), }, { + id: "who-is-the-tpr-built-for", question: "Who is the TPR built for?", answer: ( <> @@ -95,6 +104,7 @@ const ResourcesFaqPage: React.FC = () => { ), }, { + id: "what-pathways-can-i-find-in-the-tpr", question: "What pathways can I find in the TPR?", answer: ( <> @@ -111,6 +121,7 @@ const ResourcesFaqPage: React.FC = () => { ), }, { + id: "how-do-i-use-the-tpr", question: "How do I use the TPR?", answer: ( <> @@ -129,6 +140,7 @@ const ResourcesFaqPage: React.FC = () => { ), }, { + id: "does-the-tpr-tell-me-which-pathway-is-best", question: "Does the TPR tell me which pathway is best?", answer: ( <> @@ -144,6 +156,7 @@ const ResourcesFaqPage: React.FC = () => { ), }, { + id: "why-cant-i-use-one-scenario-for-everything", question: "Why can’t I use one scenario for everything?", answer: ( <> @@ -159,6 +172,7 @@ const ResourcesFaqPage: React.FC = () => { ), }, { + id: "why-do-some-pathways-have-more-benchmark-data", question: "Why do some pathways have more or different benchmark data than others?", answer: ( @@ -173,6 +187,7 @@ const ResourcesFaqPage: React.FC = () => { ), }, { + id: "what-is-a-corporate-transition-assessment", question: "What is a corporate transition assessment (CTA)?", answer: ( <> @@ -196,6 +211,7 @@ const ResourcesFaqPage: React.FC = () => { ), }, { + id: "how-does-the-tpr-relate-to-ctas", question: "How does the TPR relate to CTAs?", answer: ( <> @@ -233,6 +249,7 @@ const ResourcesFaqPage: React.FC = () => { ), }, { + id: "what-else-is-the-tpr-useful-for", question: "What else is the TPR useful for?", answer: ( <> @@ -252,50 +269,45 @@ const ResourcesFaqPage: React.FC = () => { return (
-
-
-
-
-
+ +

+ If you can’t find an answer to your question here, we would love to + hear from you. Reach out to{" "} + + Tom White + {" "} + or{" "} + + Jacob Kastl + + . +

+
-
-

- Frequently Asked Questions -

-
-

- If you can’t find an answer to your question here, we would love - to hear from you. Reach out to{" "} - - Tom White - {" "} - or{" "} - - Jacob Kastl - - . -

-
-
-
+
+
+ -
-
+
{faqItems.map((item) => ( ))}
-
+
); diff --git a/src/pages/resources/ResourcesHowToChooseAPathwayPage.test.tsx b/src/pages/resources/ResourcesHowToChooseAPathwayPage.test.tsx index f58d4fb1..730b6f37 100644 --- a/src/pages/resources/ResourcesHowToChooseAPathwayPage.test.tsx +++ b/src/pages/resources/ResourcesHowToChooseAPathwayPage.test.tsx @@ -1,21 +1,30 @@ import { describe, it, expect } from "vitest"; -import { render, screen, within } from "@testing-library/react"; +import { render, screen, within, act } from "@testing-library/react"; import { MemoryRouter } from "react-router"; import ResourcesHowToChooseAPathwayPage from "./ResourcesHowToChooseAPathwayPage"; +import { MockIntersectionObserver } from "../../test/mockIntersectionObserver"; -describe("ResourcesHowToChooseAPathwayPage — on-page index (#802)", () => { - it("lists the page's top-level sections, excluding the hero subtitle and the 'Credible does not mean suitable' aside", () => { - render( - - - , - ); +const renderPage = () => + render( + + + , + ); + +/** Top-level index entries only, skipping the "Back to top" link and any + * sub-entries that the section currently in view has expanded. */ +const topLevelEntries = (nav: HTMLElement): (string | null)[] => + Array.from(nav.querySelectorAll(":scope > ul > li > a")) + .slice(1) + .map((link) => link.textContent); + +describe("ResourcesHowToChooseAPathwayPage — on-page index (#802, #955)", () => { + it("lists the page's top-level sections, excluding the page subtitle", () => { + renderPage(); const nav = screen.getByRole("navigation", { name: "On this page" }); - // First link is the "Back to top" entry, not one of the page's sections. - const links = within(nav).getAllByRole("link").slice(1); - expect(links.map((link) => link.textContent)).toEqual([ + expect(topLevelEntries(nav)).toEqual([ "Five steps to finding the right pathway", "Pathway characteristics for different use cases", "How-to guides", @@ -23,18 +32,79 @@ describe("ResourcesHowToChooseAPathwayPage — on-page index (#802)", () => { ]); }); - it("renders the comparison guide as an h3 accordion entry, collapsed by default like a FAQ item", () => { - render( - - - , + it("nests the five steps under the section they belong to, while it is in view", () => { + renderPage(); + + const nav = screen.getByRole("navigation", { name: "On this page" }); + // "Five steps" is the first section, so it is active on load. + const subEntries = Array.from( + nav.querySelectorAll(":scope > ul > li > ul > li > a"), + ).map((link) => link.textContent); + + expect(subEntries).toEqual([ + "Define the intended application", + "Check credibility", + "Review pathway features", + "Check granularity", + "Confirm benchmark data availability", + ]); + + // Scrolling on to another section collapses them again. + const observer = MockIntersectionObserver.instances[0]; + act(() => { + observer.trigger(document.getElementById("how-to-guides")!, true); + observer.trigger(document.getElementById("five-steps")!, false); + }); + + expect(nav.querySelectorAll(":scope > ul > li > ul > li > a")).toHaveLength( + 1, ); + expect( + within(nav).getByRole("link", { + name: "How to compare pathways using the TPR", + }), + ).toBeInTheDocument(); + }); +}); + +describe("ResourcesHowToChooseAPathwayPage — no collapsed content (#955)", () => { + it("renders every step and guide expanded, with no disclosure buttons", () => { + renderPage(); - const heading = screen.getByRole("heading", { - level: 3, - name: "How to compare pathways using the TPR", + expect(screen.queryAllByRole("button")).toHaveLength(0); + + [ + "Define the intended application", + "Check credibility", + "Review pathway features", + "Check granularity", + "Confirm benchmark data availability", + "How to compare pathways using the TPR", + ].forEach((name) => { + expect( + screen.getByRole("heading", { level: 3, name }), + ).toBeInTheDocument(); }); - const button = within(heading).getByRole("button"); - expect(button).toHaveAttribute("aria-expanded", "false"); + + // Body text of a step that used to be hidden behind a click. + expect( + screen.getByText(/Does the methodology appear technically sound\?/), + ).toBeInTheDocument(); + }); + + it("keeps the 'Credible does not mean suitable' note, now inline within step 2", () => { + const { container } = renderPage(); + const text = (container.textContent ?? "").replace(/\s+/g, " "); + + expect(text).toContain("Credible does not mean suitable."); + expect(text).toContain( + "Pathway credibility is necessary, but not sufficient.", + ); + // It is no longer a heading competing with the page's real sections. + expect( + screen.queryByRole("heading", { + name: /Credible does not mean suitable/, + }), + ).not.toBeInTheDocument(); }); }); diff --git a/src/pages/resources/ResourcesHowToChooseAPathwayPage.tsx b/src/pages/resources/ResourcesHowToChooseAPathwayPage.tsx index 2ffea680..b0bb66ef 100644 --- a/src/pages/resources/ResourcesHowToChooseAPathwayPage.tsx +++ b/src/pages/resources/ResourcesHowToChooseAPathwayPage.tsx @@ -1,7 +1,8 @@ import React, { useRef } from "react"; import { Link } from "react-router"; -import AccordionItem from "../../components/AccordionItem"; +import InfoCallout from "../../components/InfoCallout"; import OnPageIndex from "../../components/OnPageIndex"; +import PageHeader from "../../components/PageHeader"; const quickStartCards = [ { @@ -44,24 +45,24 @@ const QuickStartCard: React.FC<{ priorities: string[]; }> = ({ title, priorities }) => { return ( -
-
-

+
+
+

{title}

-
-

+

+

Prioritize

    {priorities.map((priority) => (
  • - + {priority}
  • ))} @@ -71,33 +72,33 @@ const QuickStartCard: React.FC<{ ); }; -const CollapsibleRow: React.FC<{ +/** + * One of the five steps. Always visible — the step used to be collapsed + * behind a disclosure button, which also kept it out of the on-page index + * (#955). The "Step N" label sits outside the heading so the index entry + * reads as the step's title alone. + */ +const Step: React.FC<{ + id: string; + label: string; title: string; children: React.ReactNode; -}> = ({ title, children }) => { - const [stepLabel, ...titleParts] = title.split(": "); - const heading = titleParts.join(": "); - - return ( - -

    - {stepLabel} -

    - - {heading} - -
- } - panelClassName="mt-5 rounded-2xl border border-neutral-200 bg-neutral-50 p-5 text-rmigray-700 leading-7" +}> = ({ id, label, title, children }) => ( +
+

+ {label} +

+

-
- {children} -
- - ); -}; + {title} +

+
+ {children} +
+
+); const GuideScreenshot: React.FC<{ src: string; alt: string }> = ({ src, @@ -107,17 +108,19 @@ const GuideScreenshot: React.FC<{ src: string; alt: string }> = ({ src={src} alt={alt} loading="lazy" - className="w-full rounded-xl border border-neutral-200 shadow-sm" + className="w-full rounded-md border border-neutral-200 shadow-sm" /> ); type Guide = { + id: string; title: string; content: React.ReactNode; }; const guides: Guide[] = [ { + id: "how-to-compare-pathways", title: "How to compare pathways using the TPR", content: ( <> @@ -225,17 +228,18 @@ const guides: Guide[] = [ }, ]; -const GuideItemBlock: React.FC = ({ title, content }) => ( - - {title} - - } - panelClassName="mt-5 border-t border-neutral-200 pt-5 text-rmigray-700" - > -
{content}
-
+const GuideItemBlock: React.FC = ({ id, title, content }) => ( +
+

+ {title} +

+
+ {content} +
+
); const ResourcesHowToChooseAPathwayPage: React.FC = () => { @@ -243,54 +247,40 @@ const ResourcesHowToChooseAPathwayPage: React.FC = () => { return (
-
-
-
-
-
- -
-

- How to Choose a Pathway -

- -

- Five steps to identify the pathways that fit your assessment - question -

- -
-

- Different energy transition pathways answer different questions. - A pathway that is useful for assessing company target ambition - may not be the best fit for assessing policy exposure or - technology feasibility. -

-

- The Transition Pathways Repository (TPR) helps you compare - pathways in a more structured way so you can choose the ones - that are most useful for your application. -

-
-
-
+ +

+ Different energy transition pathways answer different questions. A + pathway that is useful for assessing company target ambition may not + be the best fit for assessing policy exposure or technology + feasibility. +

+

+ The Transition Pathways Repository (TPR) helps you compare pathways in + a more structured way so you can choose the ones that are most useful + for your application. +

+
-
+
+
-
-
+
+

Five steps to finding the right pathway

-

+

Selecting appropriate pathways for a transition-related question follows a straightforward process, as laid out in RMI’s{" "} @@ -306,164 +296,161 @@ const ResourcesHowToChooseAPathwayPage: React.FC = () => {

-
-
-
-

- The five-step process to selecting a pathway -

-
- -
- -

- The ‘right’ pathway depends on the use case, so it is - important to clarify what question you need the pathway - help answer. This will then inform the decisions made in - the following steps. The more specific you can be, the - easier it will be to narrow down your options. -

-

Example questions could include:

-
    -
  • - Are a company’s emissions targets still ambitious - given local policy constraints? -
  • -
  • - Are a company’s solar deployment targets feasible - given local land-use constraints? -
  • -
  • - Is a company on track to align with the NDC targets in - the jurisdictions where they operate? -
  • -
  • - What external constraints are preventing a company - from achieving their transition goals? -
  • -
-
+ +

+ The ‘right’ pathway depends on the use case, so it is + important to clarify what question you need the pathway help + answer. This will then inform the decisions made in the + following steps. The more specific you can be, the easier it + will be to narrow down your options. +

+

Example questions could include:

+
    +
  • + Are a company’s emissions targets still ambitious given + local policy constraints? +
  • +
  • + Are a company’s solar deployment targets feasible given + local land-use constraints? +
  • +
  • + Is a company on track to align with the NDC targets in the + jurisdictions where they operate? +
  • +
  • + What external constraints are preventing a company from + achieving their transition goals? +
  • +
+
- -

- Review who produced the pathway and whether it was - developed through a robust process. -

-

Useful questions include:

-
    -
  • - Was it developed by an organization with strong - technical expertise? -
  • -
  • - Was it reviewed by relevant experts or stakeholders? -
  • -
  • Does the methodology appear technically sound?
  • -
-

- Pathways hosted on the TPR are all considered credible - according to these criteria. Note that this does not - mean that experts agree with the outcomes of each - pathway, but that for the input assumptions made, the - outputs have been appropriately modeled. -

-
+ +

+ Review who produced the pathway and whether it was developed + through a robust process. +

+

Useful questions include:

+
    +
  • + Was it developed by an organization with strong technical + expertise? +
  • +
  • Was it reviewed by relevant experts or stakeholders?
  • +
  • Does the methodology appear technically sound?
  • +
+

+ Pathways hosted on the TPR are all considered credible + according to these criteria. Note that this does not mean that + experts agree with the outcomes of each pathway, but that for + the input assumptions made, the outputs have been + appropriately modeled. +

- -

- Look at the main characteristics of the pathway and - evaluate whether they are appropriate for the question - being asked. -

-

These include:

-
    -
  • Pathway type,
  • -
  • Temperature outcome,
  • -
  • Sector and geographic scope,
  • -
  • - Main drivers of change (e.g., policies or technology - costs), -
  • -
  • - Trends and outcomes (e.g., technology deployment or - emissions trends). -
  • -
-

- These features will help you interpret the results. See - the{" "} - - Methodology section - {" "} - for more details. -

-

- You can find these features in the pathway summary and - key features sections of the TPR. -

-
+ +

+ Credible does not mean suitable. Pathway credibility + is necessary, but not sufficient. A pathway can be robust + and well developed, yet still be too broad, too generic, or + missing the benchmark data needed for a given application. +

+
+
- -

- Check whether the pathway is granular enough for your - application across: -

-
    -
  • Geography
  • -
  • Technology
  • -
  • Time
  • -
-

- For example, a pathway that groups all renewables - together may not be detailed enough to assess the - feasibility of the rate of geothermal deployment. And a - pathway that provides outputs for Southeast Asia as a - whole may not be granular enough to assess policy risk - in Indonesia. -

-

- This information is available in the expert overview and - key features of each pathway in the TPR. -

-
+ +

+ Look at the main characteristics of the pathway and evaluate + whether they are appropriate for the question being asked. +

+

These include:

+
    +
  • Pathway type,
  • +
  • Temperature outcome,
  • +
  • Sector and geographic scope,
  • +
  • + Main drivers of change (e.g., policies or technology costs), +
  • +
  • + Trends and outcomes (e.g., technology deployment or + emissions trends). +
  • +
+

+ These features will help you interpret the results. See the{" "} + + Methodology section + {" "} + for more details. +

+

+ You can find these features in the pathway summary and key + features sections of the TPR. +

+
- -

- Finally, check whether the pathway provides the actual - output data you need. A pathway may model a sector in - detail but still not publish the specific benchmark - metrics needed for comparison with company data. -

-

- You can see what data is available in the pathway expert - overview, as well as the standardized data download in - the TPR. -

-
-
-
+ +

+ Check whether the pathway is granular enough for your + application across: +

+
    +
  • Geography
  • +
  • Technology
  • +
  • Time
  • +
+

+ For example, a pathway that groups all renewables together may + not be detailed enough to assess the feasibility of the rate + of geothermal deployment. And a pathway that provides outputs + for Southeast Asia as a whole may not be granular enough to + assess policy risk in Indonesia. +

+

+ This information is available in the expert overview and key + features of each pathway in the TPR. +

+
- -
+ +

+ Finally, check whether the pathway provides the actual output + data you need. A pathway may model a sector in detail but + still not publish the specific benchmark metrics needed for + comparison with company data. +

+

+ You can see what data is available in the pathway expert + overview, as well as the standardized data download in the + TPR. +

+
-
-
+
+

{

-
+
{quickStartCards.map((card) => ( {
-
-
+
+

How-to guides

-

+

Short click-through guides for specific TPR features.

-
-
- {guides.map((guide) => ( - - ))} -
-
+ {guides.map((guide) => ( + + ))}
-
-
+
+

What to do next

-
    +
    • { - const view = render( +const renderPage = () => + render( , ); - // The disclosure button's accessible name runs the title straight into the - // subtitle with no separator: "RegionsWhich regions and countries are covered?". - fireEvent.click( - screen.getByRole("button", { name: /^Regions\s*Which regions/i }), - ); - return view; -}; /** Page text with whitespace collapsed, so JSX line wrapping doesn't matter. */ const flatText = (container: HTMLElement): string => @@ -31,12 +24,12 @@ const countOf = (haystack: string, needle: string): number => describe("ResourcesMethodologyPage — Regions note", () => { it("states that region mappings come from the pathway's model, not RMI (#800)", () => { - const { container } = renderWithRegionsOpen(); + const { container } = renderPage(); expect(flatText(container)).toContain(REGION_MAPPING_DISCLAIMER); }); it("keeps the existing territorial-claims sentence in the same note", () => { - const { container } = renderWithRegionsOpen(); + const { container } = renderPage(); const text = flatText(container); expect(text).toContain(TERRITORIAL_CLAIMS); @@ -47,7 +40,7 @@ describe("ResourcesMethodologyPage — Regions note", () => { }); it("renders the note as a single italic block", () => { - const { container } = renderWithRegionsOpen(); + const { container } = renderPage(); const italics = Array.from(container.querySelectorAll("i")).filter((el) => (el.textContent ?? "").replace(/\s+/g, " ").includes(TERRITORIAL_CLAIMS), @@ -56,13 +49,11 @@ describe("ResourcesMethodologyPage — Regions note", () => { expect(flatText(italics[0])).toContain(REGION_MAPPING_DISCLAIMER); }); - it("does not show the note until the Regions subsection is expanded", () => { - const { container } = render( - - - , - ); - expect(flatText(container)).not.toContain(REGION_MAPPING_DISCLAIMER); + it("shows the note without any interaction, now that subsections no longer collapse (#955)", () => { + const { container } = renderPage(); + expect(flatText(container)).toContain(REGION_MAPPING_DISCLAIMER); + // The whole page is readable without clicking: no disclosure buttons left. + expect(screen.queryAllByRole("button")).toHaveLength(0); }); }); @@ -87,4 +78,69 @@ describe("ResourcesMethodologyPage — on-page index (#802)", () => { "What to do next", ]); }); + + it("reveals a section's subsections in the index only while that section is the one in view (#955)", () => { + renderPage(); + + const nav = screen.getByRole("navigation", { name: "On this page" }); + const entries = () => + within(nav) + .getAllByRole("link") + .map((link) => link.textContent); + + // "Key definitions" is active on load and has no subsections, so no + // sub-entries are listed anywhere yet. + expect(entries()).not.toContain("Pathway type"); + expect(entries()).not.toContain("Regions"); + + const observer = MockIntersectionObserver.instances[0]; + act(() => { + observer.trigger( + document.getElementById("meta-data-classification")!, + true, + ); + }); + + // Its own subsections expand... + expect(entries()).toContain("Pathway type"); + expect(entries()).toContain("Net zero reached"); + // ...and no other section's do. + expect(entries()).not.toContain("Regions"); + + act(() => { + observer.trigger( + document.getElementById("scope-and-granularity-classification")!, + true, + ); + observer.trigger( + document.getElementById("meta-data-classification")!, + false, + ); + }); + + expect(entries()).toContain("Regions"); + expect(entries()).not.toContain("Pathway type"); + }); + + it("marks the parent section expanded when a subsection itself is in view", () => { + renderPage(); + + const nav = screen.getByRole("navigation", { name: "On this page" }); + const observer = MockIntersectionObserver.instances[0]; + + act(() => { + observer.trigger(document.getElementById("regions")!, true); + }); + + const active = within(nav) + .getAllByRole("link") + .find((link) => link.getAttribute("aria-current")); + expect(active?.textContent).toBe("Regions"); + // Its parent's other subsections are listed alongside it. + expect( + within(nav) + .getAllByRole("link") + .map((link) => link.textContent), + ).toContain("Sectors"); + }); }); diff --git a/src/pages/resources/ResourcesMethodologyPage.tsx b/src/pages/resources/ResourcesMethodologyPage.tsx index 918a2d79..3ee0515d 100644 --- a/src/pages/resources/ResourcesMethodologyPage.tsx +++ b/src/pages/resources/ResourcesMethodologyPage.tsx @@ -1,17 +1,22 @@ -import React, { useId, useRef, useState } from "react"; +import React, { useRef } from "react"; import { Link } from "react-router"; import { REGION_MAPPING_DISCLAIMER } from "../../utils/geographyUtils"; +import InfoCallout from "../../components/InfoCallout"; import OnPageIndex from "../../components/OnPageIndex"; +import PageHeader from "../../components/PageHeader"; -const DefinitionCard: React.FC<{ +/** One of the three terms the page disambiguates up front. The three share a + * single `InfoCallout` rather than getting one each: they are one aside about + * terminology, not three separate notes, so they carry one ⓘ between them. */ +const Definition: React.FC<{ title: string; children: React.ReactNode; }> = ({ title, children }) => { return ( -
      -

      {title}

      -
      {children}
      -
      +
      +

      {title}

      +
      {children}
      +
      ); }; @@ -20,9 +25,9 @@ const DetailWithValues: React.FC<{ values: React.ReactNode; }> = ({ description, values }) => { return ( -
      +
      {description}
      -
      +
      {values}
      @@ -31,56 +36,39 @@ const DetailWithValues: React.FC<{ ); }; -const CollapsibleSubsection: React.FC<{ +/** + * A classification sub-section. Always visible — these used to be collapsed + * behind disclosure buttons, which hid most of the page's substance behind a + * click and kept every one of them out of the on-page index (#955). The `id` + * is what makes it appear in that index, nested under the section it + * belongs to, so keep it stable: it is also the deep link. + * + * The hairline above each one is what keeps a run of nine readable — they are + * siblings within their section's wrapper, so `first:` correctly drops the + * rule on the one that opens a section. It stays lighter than the section's + * own `border-neutral-300` rule so the two levels don't compete. + */ +const Subsection: React.FC<{ + id: string; title: string; subtitle?: string; children: React.ReactNode; -}> = ({ title, subtitle, children }) => { - const [isOpen, setIsOpen] = useState(false); - const contentId = useId(); - +}> = ({ id, title, subtitle, children }) => { return ( -
      -

      - +
      +

      + {title}

      - - {isOpen ? ( -
      - {children} -
      + {subtitle ? ( +

      + {subtitle} +

      ) : null} + +
      {children}
      ); }; @@ -90,91 +78,79 @@ const ResourcesMethodologyPage: React.FC = () => { return (
      -
      -
      -
      -
      -
      - -
      -

      - Methodology -

      - -

      - How the Transition Pathways Repository (TPR) classifies pathways - and how to interpret those classifications -

      + +

      + The TPR uses a structured classification approach to help analysts + compare pathways in a consistent manner. This page explains the + rationale behind these classifications and how they can be + interpreted. +

      +

      + These classifications are designed to support pathway selection and + interpretation for use cases such as transition assessments, risk + analysis, and opportunity identification. They do not determine a + single best pathway for a task; users must still understand the + underlying pathway and how it meets their needs. +

      +
      -
      -

      - The TPR uses a structured classification approach to help - analysts compare pathways in a consistent manner. This page - explains the rationale behind these classifications and how they - can be interpreted. -

      -

      - These classifications are designed to support pathway selection - and interpretation for use cases such as transition assessments, - risk analysis, and opportunity identification. They do not - determine a single best pathway for a task; users must still - understand the underlying pathway and how it meets their needs. -

      -
      -
      -
      - -
      +
      +
      -
      +

      Key definitions

      -

      +

      Throughout the TPR, you will see the use of closely related terminology when describing pathways and their content. These are the definitions used in the context of the TPR.

      -
      - -

      - A forward-looking view of how a sector, region, or economy - could change over time. -

      -

      - This covers anything from detailed models to simple policy - objectives. -

      -
      + +
      + +

      + A forward-looking view of how a sector, region, or economy + could change over time. +

      +

      + This covers anything from detailed models to simple policy + objectives. +

      +
      - -

      - A subset of pathways based on systematic modeling. Scenarios - often provide a structured set of assumptions. -

      -
      + +

      + A subset of pathways based on systematic modeling. + Scenarios often provide a structured set of assumptions. +

      +
      - -

      - A data point from a pathway that can be compared with - company data. Pathways often provide multiple relevant - benchmarks. -

      -
      -
      + +

      + A data point from a pathway that can be compared with + company data. Pathways often provide multiple relevant + benchmarks. +

      +
      +
      +
      -
      -
      +
      +

      {

      -
      -
      +
      +

      {

      -
      -
      - -
      -
      -

      - Predictive -

      -

      - All outcomes are modeled based on existing trends - without any additional assumptions. No new policies - are introduced or changed. No major technological - breakthroughs are achieved. -

      -

      - These pathways often describe business-as-usual - trajectories and can be useful as baseline benchmarks, - especially in risk applications. -

      -
      - -
      -

      - Exploratory -

      -

      - Exploratory pathways test the impact of different - assumptions that are plausible but not necessarily - predicted. They will differ from a mere continuation - of existing trends in policies, technology costs, or - demand. -

      -

      - These are often useful for assessing risks and - opportunities under alternative policy, market, or - technology conditions. Typical cases are pathways that - consider announced policies, such as NDCs. -

      -
      - -
      -

      - Normative -

      -

      - Normative pathways begin with a target end state, - often a climate outcome such as 1.5°C, and work - backward to produce a pathway consistent with that - goal. They often use optimization models. -

      -

      - These pathways are often useful for questions of - ambition. -

      -
      -
      -
      - - -
      -

      - Where available, the temperature outcome of a pathway - can be used as a proxy for pathway ambition. It refers - to the global average temperature rise above - pre-industrial levels by the end of the century. -

      -

      - Global pathways often provide a temperature outcome as - they can be linked to a carbon budget. Region- or - sector-specific pathways usually do not have a clear - temperature outcome because a global economy-wide model - is required to calculate it. +

      + +
      +
      +

      + Predictive +

      +

      + All outcomes are modeled based on existing trends + without any additional assumptions. No new policies are + introduced or changed. No major technological + breakthroughs are achieved.

      -
      - - - -
      -

      - The start year of the model refers to the last year - before any forward-looking modeling. It is the final - year of the historical baseline used in the calculations - of the pathway. +

      + These pathways often describe business-as-usual + trajectories and can be useful as baseline benchmarks, + especially in risk applications.

      -
      - -
      -

      - The end year of the model refers to the final year of - the forward-looking output generated by the model, - focusing on the available pathway metrics as a - reference. +

      +

      + Exploratory +

      +

      + Exploratory pathways test the impact of different + assumptions that are plausible but not necessarily + predicted. They will differ from a mere continuation of + existing trends in policies, technology costs, or + demand.

      -

      - For example, if the pathway metrics are available until - 2050, but there is a temperature outcome for 2100, then - the end year is 2050. +

      + These are often useful for assessing risks and + opportunities under alternative policy, market, or + technology conditions. Typical cases are pathways that + consider announced policies, such as NDCs.

      - - -
      -

      - The year in which the pathway reaches net-zero - emissions. Not all pathways reach net-zero emissions, so - this value will not always be provided. +

      +

      + Normative +

      +

      + Normative pathways begin with a target end state, often + a climate outcome such as 1.5°C, and work backward to + produce a pathway consistent with that goal. They often + use optimization models.

      -

      - In some cases, net zero will refer to a specific region - or (set of) sector(s). Where this is the case, the scope - is indicated accordingly. +

      + These pathways are often useful for questions of + ambition.

      - -
      +
      + + + +
      +

      + Where available, the temperature outcome of a pathway can + be used as a proxy for pathway ambition. It refers to the + global average temperature rise above pre-industrial + levels by the end of the century. +

      +

      + Global pathways often provide a temperature outcome as + they can be linked to a carbon budget. Region- or + sector-specific pathways usually do not have a clear + temperature outcome because a global economy-wide model is + required to calculate it. +

      +
      +
      + + +
      +

      + The start year of the model refers to the last year before + any forward-looking modeling. It is the final year of the + historical baseline used in the calculations of the + pathway. +

      +
      +
      + + +
      +

      + The end year of the model refers to the final year of the + forward-looking output generated by the model, focusing on + the available pathway metrics as a reference. +

      +

      + For example, if the pathway metrics are available until + 2050, but there is a temperature outcome for 2100, then + the end year is 2050. +

      +
      +
      + + +
      +

      + The year in which the pathway reaches net-zero emissions. + Not all pathways reach net-zero emissions, so this value + will not always be provided. +

      +

      + In some cases, net zero will refer to a specific region or + (set of) sector(s). Where this is the case, the scope is + indicated accordingly. +

      +
      +
      -
      -
      +
      +

      {

      -
      -
      - -
      -

      - The list of regions and countries covered by the - pathway. This will only list countries or regions that - the pathway provides benchmark metrics for. All - individual countries are allowed, based on ISO2 (3166) - definitions. Regions are provided as defined in the - pathway. -

      +
      + +
      +

      + The list of regions and countries covered by the pathway. + This will only list countries or regions that the pathway + provides benchmark metrics for. All individual countries + are allowed, based on ISO2 (3166) definitions. Regions are + provided as defined in the pathway. +

      + {/* Kept as a single italic block: #800 reconciled two + near-duplicate notes into this one, and the test suite + asserts exactly one italic run carries it. */} +

      Note: {REGION_MAPPING_DISCLAIMER} RMI does not make @@ -386,43 +367,45 @@ const ResourcesMethodologyPage: React.FC = () => { conflicting territorial claims.

      -
      - + +
      + - - -

      - List of sectors that the pathway covers, where - coverage means that the pathway provides at least - one relevant output metric for each of the sectors - assigned here. -

      -

      - The repository is currently limited to providing - detailed information on the power sector. Additional - sectors will be added in the near future. This - variable also clarifies which other sectors a - pathway models. -

      - - } - values={ - <> -

      The list of allowed values is:

      -
        -
      • Power
      • -
      - - } - /> -
      + + +

      + List of sectors that the pathway covers, where + coverage means that the pathway provides at least one + relevant output metric for each of the sectors + assigned here. +

      +

      + The repository is currently limited to providing + detailed information on the power sector. Additional + sectors will be added in the near future. This + variable also clarifies which other sectors a pathway + models. +

      + + } + values={ + <> +

      The list of allowed values is:

      +
        +
      • Power
      • +
      + + } + /> +
      - {/* @@ -476,33 +459,34 @@ const ResourcesMethodologyPage: React.FC = () => { } /> - */} + */} - - -

      - This field shows the technologies that a pathway - provides output for within the sector(s) that it - covers. It is returned for any sector that is - identified as in-scope in the “sectors” variable and - that has specified allowed values here. As the - sector coverage of the TPR expands, sectors that we - show technologies for will also expand. -

      - - } - values={ -
        -
      • - Power: Coal, oil, gas, nuclear, hydro, wind, solar, - biomass, renewables, other -
      • - {/*
      • + + +

        + This field shows the technologies that a pathway + provides output for within the sector(s) that it + covers. It is returned for any sector that is + identified as in-scope in the “sectors” variable and + that has specified allowed values here. As the sector + coverage of the TPR expands, sectors that we show + technologies for will also expand. +

        + + } + values={ +
          +
        • + Power: Coal, oil, gas, nuclear, hydro, wind, solar, + biomass, renewables, other +
        • + {/*
        • Steel: BOF, DRI-BOF, BF-BOF, EAF, DRI-EAF, scrap-EAF, electrolyzer/electrowinning, other
        • @@ -512,34 +496,35 @@ const ResourcesMethodologyPage: React.FC = () => { aircraft), HEFA (biofuels, including FT with biomass), PtL (incl. G/FT), AtJ, other */} -
        - } - /> - +
      + } + /> + - - -

      - List of sector-specific benchmark metrics that the - pathway reports outcomes for. Currently, this is - only defined for the power sector, but it will be - expanded to each additional sector that we fully - cover in the repository. -

      - - } - values={ -
        -
      • - Power: Emissions intensity, Absolute emissions, - Capacity, Generation, Technology mix. -
      • - {/*
      • + + +

        + List of sector-specific benchmark metrics that the + pathway reports outcomes for. Currently, this is only + defined for the power sector, but it will be expanded + to each additional sector that we fully cover in the + repository. +

        + + } + values={ +
          +
        • + Power: Emissions intensity, Absolute emissions, + Capacity, Generation, Technology mix. +
        • + {/*
        • Aviation: Emissions intensity (passenger), Emissions intensity (freight), Absolute emissions well-to-wheel (passenger), Absolute emissions well-to-wheel @@ -557,202 +542,206 @@ const ResourcesMethodologyPage: React.FC = () => { Technology mix (production by route), Steel production by technology (production route), Scrap share.
        • */} -
        - } - /> - +
      + } + /> + - - -

      - Describes which GHGs are considered in scope in the - pathway’s emissions results. This means that this - refers to absolute emissions and emissions intensity - metrics. -

      -

      - Where the scope of GHGs covered differs from the - entity the benchmark data is supposed to be compared - to (e.g., the emissions reported by a company), the - analyst may have to consider making additional - calculations or assumptions to be able to use the - pathway, or use another pathway that matches the - emissions scope. -

      - - } - values={ - <> -

      The list of allowed values is:

      -
        -
      • CO2
      • -
      • CO2e (Kyoto)
      • -
      • CO2e (CO2, methane)
      • -
      • CO2e (unspecified GHGs)
      • -
      • Other emissions scope
      • -
      • No information
      • -
      - - } - /> -
      + + +

      + Describes which GHGs are considered in scope in the + pathway’s emissions results. This means that this + refers to absolute emissions and emissions intensity + metrics. +

      +

      + Where the scope of GHGs covered differs from the + entity the benchmark data is supposed to be compared + to (e.g., the emissions reported by a company), the + analyst may have to consider making additional + calculations or assumptions to be able to use the + pathway, or use another pathway that matches the + emissions scope. +

      + + } + values={ + <> +

      The list of allowed values is:

      +
        +
      • CO2
      • +
      • CO2e (Kyoto)
      • +
      • CO2e (CO2, methane)
      • +
      • CO2e (unspecified GHGs)
      • +
      • Other emissions scope
      • +
      • No information
      • +
      + + } + /> +
      - - -

      - Lists which types of policies the pathway models. - This only refers to the policy instrument, not its - level of ambition. -

      -

      - This information is particularly relevant when - analyzing the potential impacts of a company’s - exposure to policies in the region they operate in. -

      - - } - values={ - <> -

      The list of allowed values is:

      -
        -
      • Carbon price
      • -
      • Phaseout dates
      • -
      • Subsidies
      • -
      • Target technology shares
      • -
      • Feed-in tariffs
      • -
      • Performance standards
      • -
      • Other
      • -
      - - } - /> -
      + + +

      + Lists which types of policies the pathway models. This + only refers to the policy instrument, not its level of + ambition. +

      +

      + This information is particularly relevant when + analyzing the potential impacts of a company’s + exposure to policies in the region they operate in. +

      + + } + values={ + <> +

      The list of allowed values is:

      +
        +
      • Carbon price
      • +
      • Phaseout dates
      • +
      • Subsidies
      • +
      • Target technology shares
      • +
      • Feed-in tariffs
      • +
      • Performance standards
      • +
      • Other
      • +
      + + } + /> +
      - - -

      - A list of the main new technologies covered by the - pathway that are not necessarily deployed at scale - yet but that will play a role in the future. -

      -

      - Analysts can use this as a proxy for how optimistic - the pathway is regarding technological - breakthroughs. This can be useful both as a first - step in technology risk analysis as well as in - opportunity identification. -

      - - } - values={ - <> -

      The list of allowed values is:

      -
        -
      • No new technologies
      • -
      • CCUS
      • -
      • DAC
      • -
      • Green H2/ammonia
      • -
      • SAF
      • -
      • Battery storage
      • -
      • EGS/AGS
      • -
      • Other new technologies
      • -
      • No information
      • -
      - - } - /> -
      + + +

      + A list of the main new technologies covered by the + pathway that are not necessarily deployed at scale yet + but that will play a role in the future. +

      +

      + Analysts can use this as a proxy for how optimistic + the pathway is regarding technological breakthroughs. + This can be useful both as a first step in technology + risk analysis as well as in opportunity + identification. +

      + + } + values={ + <> +

      The list of allowed values is:

      +
        +
      • No new technologies
      • +
      • CCUS
      • +
      • DAC
      • +
      • Green H2/ammonia
      • +
      • SAF
      • +
      • Battery storage
      • +
      • EGS/AGS
      • +
      • Other new technologies
      • +
      • No information
      • +
      + + } + /> +
      - - -

      - Describes the level of granularity that is available - for technology unit cost data. -

      -

      - A more granular breakdown can be useful when a - company’s future profitability and competitiveness - is to be analyzed, especially in technologies whose - costs are projected to decrease over time. -

      - - } - values={ - <> -

      The list of allowed values is:

      -
        -
      • Total costs
      • -
      • Capital costs, O&M, etc.
      • -
      • Other cost breakdown
      • -
      • No information
      • -
      - - } - /> -
      + + +

      + Describes the level of granularity that is available + for technology unit cost data. +

      +

      + A more granular breakdown can be useful when a + company’s future profitability and competitiveness is + to be analyzed, especially in technologies whose costs + are projected to decrease over time. +

      + + } + values={ + <> +

      The list of allowed values is:

      +
        +
      • Total costs
      • +
      • Capital costs, O&M, etc.
      • +
      • Other cost breakdown
      • +
      • No information
      • +
      + + } + /> +
      - - -

      - Describes the availability and granularity of the - investment needs data provided by the pathway. -

      -

      - This can indicate if the investment pipeline of a - company is on pace with assumed requirements of the - pathway. Where more detail is provided, it can also - highlight what part of the value chain needs most - investment and if that is reflected in a company’s - strategy. -

      - - } - values={ - <> -

      The list of allowed values is:

      -
        -
      • Total investment
      • -
      • By sector
      • -
      • By sector and value chain segment
      • -
      • By technology
      • -
      • By technology and value chain segment
      • -
      • No information
      • -
      - - } - /> -
      -
      + + +

      + Describes the availability and granularity of the + investment needs data provided by the pathway. +

      +

      + This can indicate if the investment pipeline of a + company is on pace with assumed requirements of the + pathway. Where more detail is provided, it can also + highlight what part of the value chain needs most + investment and if that is reflected in a company’s + strategy. +

      + + } + values={ + <> +

      The list of allowed values is:

      +
        +
      • Total investment
      • +
      • By sector
      • +
      • By sector and value chain segment
      • +
      • By technology
      • +
      • By technology and value chain segment
      • +
      • No information
      • +
      + + } + /> +
      -
      -
      +
      +

      {

      -
      -
      - - -

      - Summarizes how GHG emissions change in the pathway - between the start year of the model and the end year - of the model. -

      -

      - The underlying calculation is made using the - compound annual growth rate. -

      - {/*

      +

      + + +

      + Summarizes how GHG emissions change in the pathway + between the start year of the model and the end year + of the model. +

      +

      + The underlying calculation is made using the compound + annual growth rate. +

      + {/*

      Comes with an indication of regional and sectoral scope.

      */} - - } - values={ - <> -

      The list of allowed values is:

      -
        -
      • Significant increase
      • -
      • Moderate increase
      • -
      • Minor increase
      • -
      • Low or no change
      • -
      • Minor decrease
      • -
      • Moderate decrease
      • -
      • Significant decrease
      • -
      • No information
      • -
      - - } - /> - + + } + values={ + <> +

      The list of allowed values is:

      +
        +
      • Significant increase
      • +
      • Moderate increase
      • +
      • Minor increase
      • +
      • Low or no change
      • +
      • Minor decrease
      • +
      • Moderate decrease
      • +
      • Significant decrease
      • +
      • No information
      • +
      + + } + /> +
      - - -

      - Summarizes how energy efficiency, defined as energy - use per USD GDP output, changes in the pathway - between the start year of the model and the end year - of the model. -

      -

      - The underlying calculation is made using the - compound annual growth rate. -

      - {/*

      + + +

      + Summarizes how energy efficiency, defined as energy + use per USD GDP output, changes in the pathway between + the start year of the model and the end year of the + model. +

      +

      + The underlying calculation is made using the compound + annual growth rate. +

      + {/*

      Comes with an indication of regional and sectoral scope.

      */} - - } - values={ - <> -

      The list of allowed values is:

      -
        -
      • Significant deterioration
      • -
      • Moderate deterioration
      • -
      • Minor deterioration
      • -
      • Low or no change
      • -
      • Minor improvement
      • -
      • Moderate improvement
      • -
      • Significant improvement
      • -
      • No information
      • -
      - - } - /> -
      + + } + values={ + <> +

      The list of allowed values is:

      +
        +
      • Significant deterioration
      • +
      • Moderate deterioration
      • +
      • Minor deterioration
      • +
      • Low or no change
      • +
      • Minor improvement
      • +
      • Moderate improvement
      • +
      • Significant improvement
      • +
      • No information
      • +
      + + } + /> + - - -

      - Summarizes how energy demand changes in the pathway - between the start year of the model and the end year - of the model. -

      -

      - The underlying calculation is made using the - compound annual growth rate. -

      - {/*

      + + +

      + Summarizes how energy demand changes in the pathway + between the start year of the model and the end year + of the model. +

      +

      + The underlying calculation is made using the compound + annual growth rate. +

      + {/*

      Comes with an indication of regional and sectoral scope.

      */} - - } - values={ - <> -

      The list of allowed values is:

      -
        -
      • Significant increase
      • -
      • Moderate increase
      • -
      • Minor increase
      • -
      • Low or no change
      • -
      • Minor decrease
      • -
      • Moderate decrease
      • -
      • Significant decrease
      • -
      • No information
      • -
      - - } - /> -
      + + } + values={ + <> +

      The list of allowed values is:

      +
        +
      • Significant increase
      • +
      • Moderate increase
      • +
      • Minor increase
      • +
      • Low or no change
      • +
      • Minor decrease
      • +
      • Moderate decrease
      • +
      • Significant decrease
      • +
      • No information
      • +
      + + } + /> + - - -

      - Summarizes how the share of electricity in energy - end use changes in the pathway between the start - year of the model and the end year of the model. -

      -

      - The underlying calculation is made using the - compound annual growth rate. -

      - {/*

      + + +

      + Summarizes how the share of electricity in energy end + use changes in the pathway between the start year of + the model and the end year of the model. +

      +

      + The underlying calculation is made using the compound + annual growth rate. +

      + {/*

      Comes with an indication of regional and sectoral scope.

      */} - - } - values={ - <> -

      The list of allowed values is:

      -
        -
      • Significant increase
      • -
      • Moderate increase
      • -
      • Minor increase
      • -
      • Low or no change
      • -
      • Minor decrease
      • -
      • Moderate decrease
      • -
      • Significant decrease
      • -
      • No information
      • -
      - - } - /> -
      + + } + values={ + <> +

      The list of allowed values is:

      +
        +
      • Significant increase
      • +
      • Moderate increase
      • +
      • Minor increase
      • +
      • Low or no change
      • +
      • Minor decrease
      • +
      • Moderate decrease
      • +
      • Significant decrease
      • +
      • No information
      • +
      + + } + /> + - - -

      - Describes the level of ambition of the policies - modelled in the pathway. This is a key - differentiator for many pathways. -

      -

      - From a risk angle, ambition closer to - “current/legislated” is more likely to occur, but - not the only plausible future outcome. Higher - ambition pathways often come with remaining - implementation gaps, which means they can be a good - basis to understand bottlenecks for the transition - of a sector. These, in turn, can be opportunities in - some cases, or indicators of where to intervene. -

      - - } - values={ - <> -

      The list of allowed values is:

      -
        -
      • No policies included
      • -
      • Current/legislated policies
      • -
      • Current and drafted policies
      • -
      • NDCs, unconditional only
      • -
      • NDCs including conditional targets
      • -
      • High-ambition policies
      • -
      • Other policy ambition
      • -
      • No information
      • -
      - - } - /> -
      + + +

      + Describes the level of ambition of the policies + modelled in the pathway. This is a key differentiator + for many pathways. +

      +

      + From a risk angle, ambition closer to + “current/legislated” is more likely to occur, but not + the only plausible future outcome. Higher ambition + pathways often come with remaining implementation + gaps, which means they can be a good basis to + understand bottlenecks for the transition of a sector. + These, in turn, can be opportunities in some cases, or + indicators of where to intervene. +

      + + } + values={ + <> +

      The list of allowed values is:

      +
        +
      • No policies included
      • +
      • Current/legislated policies
      • +
      • Current and drafted policies
      • +
      • NDCs, unconditional only
      • +
      • NDCs including conditional targets
      • +
      • High-ambition policies
      • +
      • Other policy ambition
      • +
      • No information
      • +
      + + } + /> +
      - - -

      - Describes the direction of unit costs of the - low-carbon technologies of in-scope sectors. This - shows if/when newer low-carbon technologies are - assumed to become more competitive compared to their - high-carbon counterparts. -

      - - } - values={ - <> -

      The list of allowed values is:

      -
        -
      • Increase
      • -
      • Low or no change
      • -
      • Decrease
      • -
      • No information
      • -
      - - } - /> -
      -
      + + +

      + Describes the direction of unit costs of the + low-carbon technologies of in-scope sectors. This + shows if/when newer low-carbon technologies are + assumed to become more competitive compared to their + high-carbon counterparts. +

      + + } + values={ + <> +

      The list of allowed values is:

      +
        +
      • Increase
      • +
      • Low or no change
      • +
      • Decrease
      • +
      • No information
      • +
      + + } + /> +
      -
      -
      +
      +

      What to do next

      -

      +

      Visit the{" "} -

      +

      Insight #1: Global ambition and regional context provide different insights -

      +

      IEA NZE and GECO 1.5°C are global pathways. They provide benchmarks for understanding the scale and pace of change required to meet @@ -106,7 +114,7 @@ const posts: UpdatePost[] = [ src="/updates/how-to-comparison-view-1.png" alt="Comparison view showing pathway summary cards and policy assumptions side by side." loading="lazy" - className="w-full rounded-xl border border-neutral-200 shadow-sm" + className="w-full rounded-md border border-neutral-200 shadow-sm" />

      GECO 1.5°C may be most appropriate for testing whether a Southeast @@ -121,10 +129,10 @@ const posts: UpdatePost[] = [ for the question being assessed and where multiple pathways may be needed.

      -

      +

      Insight #2: Similar pathway goals can produce different regional benchmarks -

      +

Comparing GECO 1.5°C and ACE CNS at the Southeast Asia regional level shows that pathways with similarly ambitious long-term goals can still @@ -148,7 +156,7 @@ const posts: UpdatePost[] = [ src="/updates/how-to-comparison-view-2.png" alt="Comparison view showing pathway summary cards and benchmark capacity plots side by side." loading="lazy" - className="w-full rounded-xl border border-neutral-200 shadow-sm" + className="w-full rounded-md border border-neutral-200 shadow-sm" />

These differences reflect the pathways’ assumptions around regional @@ -188,6 +196,7 @@ const posts: UpdatePost[] = [ ), }, { + id: "four-lessons-southeast-asia-power", title: "Four lessons learned from evaluating the transition pathway landscape in the Southeast Asia power sector", date: "May 15, 2026", @@ -270,6 +279,7 @@ const posts: UpdatePost[] = [ ), }, { + id: "introducing-the-tpr", title: "Introducing the Transition Pathways Repository: Making Transition Analysis Actionable", date: "December 11, 2025", @@ -326,67 +336,55 @@ const posts: UpdatePost[] = [ ]; const ResourcesUpdatesPage: React.FC = () => { + const contentRef = useRef(null); + return (

-
-
-
-
-
- -
-

- Updates -

- -

- Check out the latest from our team on the Transition Pathways - Repository (TPR) and its use cases. -

-
-
+ - {/* No OnPageIndex on this page (#802): it has only one top-level h2 - ("Latest updates" below), so a table of contents would have - nothing to navigate to. */} -
-
-

- Latest updates -

-
+
+ {/* Each post is its own top-level heading, so the index doubles as a + list of updates. Supersedes the note from #802, which applied + when the whole feed sat under a single "Latest updates" h2. */} +
+ - {posts.length > 0 ? ( -
- {posts.map((post) => ( +
+ {posts.length > 0 ? ( + posts.map((post) => (
-
-

- {post.title} -

-

- {post.date} -

-
+

+ {post.title} +

+

+ {post.date} +

-
+
{post.body}
- ))} -
- ) : ( -
+ )) + ) : (

No posts have been added yet. When you are ready, add a new entry to the `posts` array at the top of this file.

-
- )} -
+ )} +
+
); diff --git a/src/pages/resources/ResourcesUseCasesPage.tsx b/src/pages/resources/ResourcesUseCasesPage.tsx index ccaaa9ce..d6ff00ea 100644 --- a/src/pages/resources/ResourcesUseCasesPage.tsx +++ b/src/pages/resources/ResourcesUseCasesPage.tsx @@ -1,5 +1,6 @@ import React, { useRef } from "react"; import OnPageIndex from "../../components/OnPageIndex"; +import PageHeader from "../../components/PageHeader"; const financialInstitutionRoles = [ { @@ -89,27 +90,27 @@ const UseCaseCard: React.FC = ({ bullets, }) => { return ( -
-
+
+
-

{title}

-

+

{title}

+

{subtitle}

-
+
    {bullets.map((bullet) => (
  • - + {bullet}
  • ))} @@ -124,7 +125,7 @@ const AudienceCard: React.FC<{ children: React.ReactNode; }> = ({ title, children }) => { return ( -
    +

    {title}

    {children}
    @@ -136,47 +137,32 @@ const ResourcesUseCasesPage: React.FC = () => { return (
    -
    -
    -
    -
    -
    + +

    + The energy transition is a technological revolution with dramatic + implications for global markets, international development, and + geopolitics. It presents one of the greatest economic and industrial + opportunities in history, and creates risks for those that fail to + adapt and plan accordingly. Transition pathways provide one valuable + tool for navigating this uncertainty. It does this by modeling what + the future might hold across different regions and sectors under + different assumption sets. +

    +
    -
    -

    - Use cases of RMI’s Transition Pathways Repository -

    - -

    - Transition pathways are critical to understanding how the energy - transition might unfold -

    - -
    -

    - The energy transition is a technological revolution with - dramatic implications for global markets, international - development, and geopolitics. It presents one of the greatest - economic and industrial opportunities in history, and creates - risks for those that fail to adapt and plan accordingly. - Transition pathways provide one valuable tool for navigating - this uncertainty. It does this by modeling what the future might - hold across different regions and sectors under different - assumption sets. -

    -
    -
    -
    - -
    +
    +
    -
    -
    +
    +

    {

    -
    +
    {financialInstitutionRoles.map((role) => (

    {role.title} @@ -232,8 +218,8 @@ const ResourcesUseCasesPage: React.FC = () => {

    -
    -
    +
    +

    {

    -
    -
    +
    +

    {

    -
    -
    +
    +

    Looking for more information?

    -

    +

    Find RMI’s thought leadership related to transition intelligence, pathway selection, and other transition finance topics at RMI’s{" "}