From 192bc47cf9b7a00b9fe091010066c12202bc7f5c Mon Sep 17 00:00:00 2001 From: jacobvjk Date: Fri, 9 Oct 2026 11:42:24 +0200 Subject: [PATCH 1/3] docs(design): add UI kit and design tokens, generate colour tokens from index.css Add docs/design/ so the designer can find the UI reference next to the code: - tpr-ui-kit.html: visual reference of tokens and components, incl. v2 (#937) - tpr-design-tokens.json: Tokens Studio file for Figma - README.md: how to open the kit, and what is automatic vs. manual The colour group of the token file is generated from the @theme block in src/index.css by `npm run tokens:build`; other groups and colour descriptions are hand-maintained and carried over. A new CI job in node-build-committed-files.yml fails when the committed file is stale. Nothing in docs/ is bundled into the site build. Co-Authored-By: Claude Opus 5.5 --- .../workflows/node-build-committed-files.yml | 31 + docs/design/README.md | 48 + docs/design/tpr-design-tokens.json | 417 ++ docs/design/tpr-ui-kit.html | 4621 +++++++++++++++++ package.json | 3 +- scripts/build-design-tokens.test.ts | 80 + scripts/build-design-tokens.ts | 108 + 7 files changed, 5307 insertions(+), 1 deletion(-) create mode 100644 docs/design/README.md create mode 100644 docs/design/tpr-design-tokens.json create mode 100644 docs/design/tpr-ui-kit.html create mode 100644 scripts/build-design-tokens.test.ts create mode 100644 scripts/build-design-tokens.ts diff --git a/.github/workflows/node-build-committed-files.yml b/.github/workflows/node-build-committed-files.yml index 781902d1..c8784038 100644 --- a/.github/workflows/node-build-committed-files.yml +++ b/.github/workflows/node-build-committed-files.yml @@ -37,3 +37,34 @@ jobs: echo "No changes found after generating." exit 0 fi + + design-tokens: + name: "Check design tokens match src/index.css" + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Setup Node.js + uses: actions/setup-node@v7 + with: + node-version-file: .nvmrc + cache: "npm" + cache-dependency-path: package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Regenerate design tokens + run: npm run tokens:build + + - name: Compare Results + run: | + if git diff --quiet HEAD -- docs/design/tpr-design-tokens.json; then + echo "Design tokens match src/index.css." + else + echo "::error::docs/design/tpr-design-tokens.json is out of date with src/index.css. Run 'npm run tokens:build' and commit the result." + git --no-pager diff HEAD -- docs/design/tpr-design-tokens.json + exit 1 + fi diff --git a/docs/design/README.md b/docs/design/README.md new file mode 100644 index 00000000..79515b9d --- /dev/null +++ b/docs/design/README.md @@ -0,0 +1,48 @@ +# TPR design reference + +Design files for the Transition Pathways Repository, kept next to the code they describe. Nothing in this folder is part of the website: it is never built, deployed, or shown to users. + +| File | What it is | +| -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [`tpr-design-tokens.json`](tpr-design-tokens.json) | Every brand color, plus type sizes, weights, corner radius, and shadows, in a format Figma can import (via the Tokens Studio plugin). | +| [`tpr-ui-kit.html`](tpr-ui-kit.html) | A visual reference page showing every reusable interface component: badges, buttons, cards, tables, and so on, each labeled with the code names developers use. | + +## Opening the UI kit + +GitHub shows HTML files as code rather than as a page. To view it: + +1. Open [`tpr-ui-kit.html`](tpr-ui-kit.html) on GitHub. +2. Click the **Download raw file** button (the downward arrow, top right of the file). +3. Double-click the downloaded file. It opens in your browser. + +The page's last section, **Using this in Figma**, walks through importing the colors and components. + +## For the designer: what stays up to date by itself, and what doesn't + +### ✅ Automatic: the colors in `tpr-design-tokens.json` + +The colors in this file are copied straight from the app's code by a script, never typed by hand. Every proposed code change is checked automatically on GitHub: if a developer changes a color and this file no longer matches, the change is blocked until they update it. + +**What this means for you:** the colors in this file always match the app's code on the same branch. You can trust it without double-checking. This folder currently lives on the `epic/v2` branch, so it reflects the v2 redesign rather than the version users see today. + +**What it doesn't do:** it doesn't update Figma. When colors change, re-import this file into Tokens Studio to pick up the new values. Checking this file's history on GitHub (the **History** button) shows you when it last changed. + +### ✋ Manual: everything else + +These parts are maintained by hand, so they can fall behind the code. When the app's look changes, someone needs to update them. + +| What | Why it's manual | When to update it | +| ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **The UI kit page** (`tpr-ui-kit.html`) | It's a hand-drawn picture of the components, not the components themselves. No script can redraw it. | When a change adds, removes, or restyles a component. Ask the developer making the change to update the kit in the same pull request, or flag it for you. | +| **Type sizes, weights, corner radius, shadows** in the token file | These are standard defaults that the app's code doesn't define, so there's nothing for the script to copy from. They rarely change. | Only if the team deliberately changes them, for example by adopting a brand typeface. | +| **The "Last checked" line below** | It records when someone last compared the UI kit against the real app. | Every time the UI kit is updated. | +| **Your Figma file** | Figma lives outside this repository. | After any change to either file here. | + +**Last checked:** October 2026, against the v2 redesign in [RMI/tpr#937](https://github.com/RMI/tpr/pull/937). + +> **Note on v2:** items in the UI kit tagged **v2** come from #937, the v2 redesign on the `epic/v2` branch, which had not been merged into `main` when this was written. Once it merges, remove the "not yet live" note at the top of the UI kit and update the line above. + +## For developers + +- **Changed a color in `src/index.css`?** Run `npm run tokens:build` and commit the updated `docs/design/tpr-design-tokens.json`. CI fails until you do. +- **Changed how a component looks?** Update `tpr-ui-kit.html` to match (or tell the designer), and update the **Last checked** line above. diff --git a/docs/design/tpr-design-tokens.json b/docs/design/tpr-design-tokens.json new file mode 100644 index 00000000..332d4c4c --- /dev/null +++ b/docs/design/tpr-design-tokens.json @@ -0,0 +1,417 @@ +{ + "color": { + "bluespruce": { + "value": "#003b63", + "type": "color", + "description": "Primary brand color. Header, footer, primary headings, card CTA text." + }, + "energy": { + "value": "#45cfcc", + "type": "color", + "description": "Primary accent. Primary buttons, active states, links on hover." + }, + "basalt": { + "value": "#58595b", + "type": "color", + "description": "Primary neutral, used for borders on dark surfaces (e.g. footer border)." + }, + "energy-100": { + "value": "#b6f2f1", + "type": "color" + }, + "energy-400": { + "value": "#7be8e6", + "type": "color" + }, + "energy-700": { + "value": "#2ab5b2", + "type": "color" + }, + "energy-800": { + "value": "#118280", + "type": "color" + }, + "success-400": { + "value": "#5ce5a1", + "type": "color" + }, + "success-600": { + "value": "#00cc66", + "type": "color" + }, + "success-800": { + "value": "#00994d", + "type": "color" + }, + "error-400": { + "value": "#f7988b", + "type": "color" + }, + "error-600": { + "value": "#df4e39", + "type": "color" + }, + "error-800": { + "value": "#ab3c2c", + "type": "color" + }, + "neutral-50": { + "value": "#f4f7f9", + "type": "color" + }, + "neutral-100": { + "value": "#ecf1f4", + "type": "color" + }, + "neutral-200": { + "value": "#e4e9ee", + "type": "color" + }, + "neutral-300": { + "value": "#d6dde2", + "type": "color" + }, + "neutral-400": { + "value": "#bdc9d3", + "type": "color" + }, + "neutral-500": { + "value": "#b3bcc5", + "type": "color" + }, + "neutral-600": { + "value": "#86929d", + "type": "color" + }, + "neutral-700": { + "value": "#646e77", + "type": "color" + }, + "neutral-800": { + "value": "#3b444d", + "type": "color" + }, + "neutral-900": { + "value": "#1d2328", + "type": "color" + }, + "slate": { + "value": "#92aec5", + "type": "color" + }, + "pine": { + "value": "#135908", + "type": "color" + }, + "donate": { + "value": "#00cc66", + "type": "color" + }, + "solar": { + "value": "#ffcb00", + "type": "color" + }, + "deficient": { + "value": "#e04d39", + "type": "color" + }, + "coal": { + "value": "#323232", + "type": "color" + }, + "calm": { + "value": "#e1e6f0", + "type": "color" + }, + "solar-100": { + "value": "#ffeda9", + "type": "color" + }, + "solar-200": { + "value": "#ffe480", + "type": "color" + }, + "solar-400": { + "value": "#ffdd00", + "type": "color" + }, + "solar-800": { + "value": "#c85c19", + "type": "color" + }, + "pinishgreen-100": { + "value": "#b2e6b2", + "type": "color" + }, + "pinishgreen-200": { + "value": "#99d699", + "type": "color" + }, + "pinishgreen-400": { + "value": "#408026", + "type": "color" + }, + "pinishgreen-800": { + "value": "#11431b", + "type": "color" + }, + "rmipurple-100": { + "value": "#c7c4e2", + "type": "color" + }, + "rmipurple-200": { + "value": "#a59fce", + "type": "color" + }, + "rmipurple-400": { + "value": "#7b76ad", + "type": "color" + }, + "rmipurple-800": { + "value": "#3e3969", + "type": "color" + }, + "rmiblue-100": { + "value": "#c9e3f0", + "type": "color" + }, + "rmiblue-200": { + "value": "#9bcce3", + "type": "color" + }, + "rmiblue-400": { + "value": "#529cba", + "type": "color" + }, + "rmiblue-800": { + "value": "#005d7f", + "type": "color" + }, + "rmired-100": { + "value": "#eec7b7", + "type": "color" + }, + "rmired-200": { + "value": "#df897b", + "type": "color" + }, + "rmired-400": { + "value": "#c4151c", + "type": "color" + }, + "rmired-800": { + "value": "#790000", + "type": "color" + }, + "rmipink-100": { + "value": "#f0c9de", + "type": "color", + "description": "Rose family, added in v2 (RMI/tpr#937) for sector-segment badges. Badge fill." + }, + "rmipink-200": { + "value": "#e39bc4", + "type": "color", + "description": "Sector-segment badge border." + }, + "rmipink-400": { + "value": "#ba5292", + "type": "color" + }, + "rmipink-800": { + "value": "#7f0053", + "type": "color", + "description": "Sector-segment badge text." + }, + "rmigray-100": { + "value": "#e6e7e8", + "type": "color" + }, + "rmigray-200": { + "value": "#a7a9ac", + "type": "color" + }, + "rmigray-300": { + "value": "#808284", + "type": "color" + }, + "rmigray-400": { + "value": "#58585b", + "type": "color" + }, + "rmigray-500": { + "value": "#414142", + "type": "color" + }, + "rmigray-600": { + "value": "#333333", + "type": "color" + }, + "rmigray-700": { + "value": "#2a2a2b", + "type": "color" + }, + "rmigray-800": { + "value": "#231f20", + "type": "color" + } + }, + "fontFamilies": { + "sans": { + "value": "ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'", + "type": "fontFamilies", + "description": "No brand typeface is defined in the codebase yet — this is Tailwind's default system-font stack, inherited as-is." + }, + "mono": { + "value": "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace", + "type": "fontFamilies" + } + }, + "fontSize": { + "2xs": { + "value": "10px", + "type": "fontSizes", + "description": "Not a theme token: written as the arbitrary value text-[10px] in code. Used for scope-axis labels and their Show all / Show fewer control (v2)." + }, + "xs": { + "value": "12px", + "type": "fontSizes" + }, + "sm": { + "value": "14px", + "type": "fontSizes" + }, + "base": { + "value": "16px", + "type": "fontSizes" + }, + "lg": { + "value": "18px", + "type": "fontSizes" + }, + "xl": { + "value": "20px", + "type": "fontSizes" + }, + "2xl": { + "value": "24px", + "type": "fontSizes" + }, + "3xl": { + "value": "30px", + "type": "fontSizes" + }, + "4xl": { + "value": "36px", + "type": "fontSizes" + }, + "5xl": { + "value": "48px", + "type": "fontSizes" + } + }, + "lineHeight": { + "xs": { + "value": "16px", + "type": "lineHeights" + }, + "sm": { + "value": "20px", + "type": "lineHeights" + }, + "base": { + "value": "24px", + "type": "lineHeights" + }, + "lg": { + "value": "28px", + "type": "lineHeights" + }, + "xl": { + "value": "28px", + "type": "lineHeights" + }, + "2xl": { + "value": "32px", + "type": "lineHeights" + }, + "3xl": { + "value": "36px", + "type": "lineHeights" + }, + "4xl": { + "value": "40px", + "type": "lineHeights" + } + }, + "fontWeight": { + "normal": { + "value": "400", + "type": "fontWeights" + }, + "medium": { + "value": "500", + "type": "fontWeights" + }, + "semibold": { + "value": "600", + "type": "fontWeights" + }, + "bold": { + "value": "700", + "type": "fontWeights" + } + }, + "borderRadius": { + "sm": { + "value": "4px", + "type": "borderRadius" + }, + "md": { + "value": "6px", + "type": "borderRadius" + }, + "lg": { + "value": "8px", + "type": "borderRadius" + }, + "full": { + "value": "9999px", + "type": "borderRadius" + } + }, + "boxShadow": { + "sm": { + "value": { + "x": "0", + "y": "1", + "blur": "3", + "spread": "0", + "color": "rgba(0,0,0,0.1)", + "type": "dropShadow" + }, + "type": "boxShadow" + }, + "md": { + "value": { + "x": "0", + "y": "4", + "blur": "6", + "spread": "-1", + "color": "rgba(0,0,0,0.1)", + "type": "dropShadow" + }, + "type": "boxShadow" + }, + "lg": { + "value": { + "x": "0", + "y": "10", + "blur": "15", + "spread": "-3", + "color": "rgba(0,0,0,0.1)", + "type": "dropShadow" + }, + "type": "boxShadow" + } + } +} diff --git a/docs/design/tpr-ui-kit.html b/docs/design/tpr-ui-kit.html new file mode 100644 index 00000000..aba9301d --- /dev/null +++ b/docs/design/tpr-ui-kit.html @@ -0,0 +1,4621 @@ + + + + + + Pathways UI Kit + + + +
+ + +
+
+

Transition Pathways Repository · UI reference

+

Pathways UI Kit

+

+ Every color, type size, and reusable interface component currently + shipping in the TPR front end, pulled directly from + src/index.css and src/components/ — not + redrawn from memory. Each entry carries the real Tailwind class + names and CSS custom properties, so a Figma file built from this + stays traceable back to the code that renders it. +

+

+ Component previews sit on a fixed light stage regardless of the + reading theme below, so colors always read exactly as they do in the + shipped app. +

+
+ Updated for v2. This edition includes the redesign + in + RMI/tpr#937 + (epic/v2): a new rose color family, a sector-segment + badge, selectable badges, tabbed detail pages, scope ribbons, data + tables, and grouped panels. Entries marked + v2 + come from that pull request, which was + still open (not merged) at the time of this + snapshot — treat them as the target design, not yet live. +
+
+ Source src/index.css, src/components/*.tsx + Framework Tailwind CSS v4 + Scope UI atoms & molecules (charts + excluded) + Snapshot Oct 2026, incl. RMI/tpr#937 — not + auto-synced +
+
+ + +
+

Color tokens

+

+ Defined once as CSS custom properties in a Tailwind + @theme block, then consumed everywhere as utility + classes (bg-energy, + text-rmigray-800, …). Import the hex values + below as Figma variables and the two systems stay in lockstep. +

+ +

Primary

+
+
+
+
+

Bluespruce

+

#003B63

+ --color-bluespruce +
+
+
+
+
+

Energy

+

#45CFCC

+ --color-energy +
+
+
+
+
+

Basalt

+

#58595B

+ --color-basalt +
+
+
+ +

Accent (energy scale)

+
+
+
+
+

energy-100

+

#B6F2F1

+ bg-energy-100 +
+
+
+
+
+

energy-400

+

#7BE8E6

+ bg-energy-400 +
+
+
+
+
+

energy-700

+

#2AB5B2

+ bg-energy-700 +
+
+
+
+
+

energy-800

+

#118280

+ bg-energy-800 +
+
+
+ +

Semantic — success / error

+
+
+
+
+

success-400

+

#5CE5A1

+ bg-success-400 +
+
+
+
+
+

success-600

+

#00CC66

+ bg-success-600 +
+
+
+
+
+

success-800

+

#00994D

+ bg-success-800 +
+
+
+
+
+

error-400

+

#F7988B

+ bg-error-400 +
+
+
+
+
+

error-600

+

#DF4E39

+ bg-error-600 +
+
+
+
+
+

error-800

+

#AB3C2C

+ bg-error-800 +
+
+
+ +

+ Neutral ramp — page backgrounds, borders, disabled states +

+
+
+ 50 +
+
+ 100 +
+
+ 200 +
+
+ 300 +
+
+ 400 +
+
+ 500 +
+
+ 600 +
+
+ 700 +
+
+ 800 +
+
+ 900 +
+
+
+ #F4F7F9#ECF1F4#E4E9EE#D6DDE2#BDC9D3#B3BCC5#86929D#646E77#3B444D#1D2328 +
+ +

Body text ramp — rmigray

+
+
+ 100 +
+
+ 200 +
+
+ 300 +
+
+ 400 +
+
+ 500 +
+
+ 600 +
+
+ 700 +
+
+ 800 +
+
+
+ #E6E7E8#A7A9AC#808284#58585B#414142#333333#2A2A2B#231F20 +
+ +

Secondary brand set

+
+
+
+
+

Slate

+

#92AEC5

+ bg-slate +
+
+
+
+
+

Pine

+

#135908

+ bg-pine +
+
+
+
+
+

Donate

+

#00CC66

+ bg-donate +
+
+
+
+
+

Solar

+

#FFCB00

+ bg-solar +
+
+
+
+
+

Deficient

+

#E04D39

+ bg-deficient +
+
+
+
+
+

Coal

+

#323232

+ bg-coal +
+
+
+
+
+

Calm

+

#E1E6F0

+ bg-calm +
+
+
+ +

+ Badge / plotting families — each ships 100 · 200 · 400 · 800 shades +

+
+
+
+
+

solar-100

+

#FFEDA9

+
+
+
+
+
+

solar-200

+

#FFE480

+
+
+
+
+
+

solar-400

+

#FFDD00

+
+
+
+
+
+

solar-800

+

#C85C19

+
+
+
+
+
+

pinishgreen-100

+

#B2E6B2

+
+
+
+
+
+

pinishgreen-200

+

#99D699

+
+
+
+
+
+

pinishgreen-400

+

#408026

+
+
+
+
+
+

pinishgreen-800

+

#11431B

+
+
+
+
+
+

rmipurple-100

+

#C7C4E2

+
+
+
+
+
+

rmipurple-200

+

#A59FCE

+
+
+
+
+
+

rmipurple-400

+

#7B76AD

+
+
+
+
+
+

rmipurple-800

+

#3E3969

+
+
+
+
+
+

rmiblue-100

+

#C9E3F0

+
+
+
+
+
+

rmiblue-200

+

#9BCCE3

+
+
+
+
+
+

rmiblue-400

+

#529CBA

+
+
+
+
+
+

rmiblue-800

+

#005D7F

+
+
+
+
+
+

rmired-100

+

#EEC7B7

+
+
+
+
+
+

rmired-200

+

#DF897B

+
+
+
+
+
+

rmired-400

+

#C4151C

+
+
+
+
+
+

rmired-800

+

#790000

+
+
+
+ +

+ Rose family — rmipink v2 +

+

+ Added for sector-segment badges, which needed a family no existing + one was free to supply. Sits between rmired and rmipurple in hue so + it reads as distinct from both. Same four-step structure: 100 fill, + 200 border, 400 mid, 800 text. +

+
+
+
+
+

rmipink-100

+

#F0C9DE

+ bg-rmipink-100 +
+
+
+
+
+

rmipink-200

+

#E39BC4

+ border-rmipink-200 +
+
+
+
+
+

rmipink-400

+

#BA5292

+ --color-rmipink-400 +
+
+
+
+
+

rmipink-800

+

#7F0053

+ text-rmipink-800 +
+
+
+
+ + +
+

Typography

+

+ No brand typeface is defined anywhere in the codebase — no + @font-face, no webfont link. Every screen renders on + the browser's own system font. That's a real gap, not a hidden + setting: worth a deliberate decision, not an assumption. +

+ +
+ Current stack (unset by design intent): + ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe + UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" + — Tailwind's default, inherited automatically because + tailwind.config.ts never overrides + fontFamily. +
+ +

Type scale in use

+
+
+
+ text-5xlPathways48 / 48 +
+
+ text-4xlPathways36 / 40 +
+
+ text-3xlPathways30 / 36 +
+
+ text-2xlPathways24 / 32 +
+
+ text-xlPathways20 / 28 +
+
+ text-lgPathways18 / 28 +
+
+ text-basePathways Repository16 / 24 +
+
+ text-smPathways Repository14 / 20 +
+
+ text-xsPathways Repository12 / 16 +
+
+ text-[10px]Sector · Geographyv210 / — +
+

+ The 10px size is an arbitrary Tailwind value, not a theme token. + It is used only for scope-axis labels (with + uppercase tracking-wider font-semibold) and their + "Show all" control. Exported as fontSize.2xs in the + token file. +

+
+
+ +

Weights in use

+
+
+ Aa +
+ font-normal · 400 +
+
+
+ Aa +
+ font-medium · 500 +
+
+
+ Aa +
+ font-semibold · 600 +
+
+
+ Aa +
+ font-bold · 700 +
+
+
+
+ + +
+

Spacing & elevation

+

+ Also Tailwind defaults (a 4px base unit) — flagged here because + components lean on a small, consistent subset of the radius and + shadow scale. +

+ +

Corner radius

+
+
+
+
+ rounded-sm · 4px +
+
+
+
+
+ rounded-md · 6px +
+
+
+
+
+ rounded-lg · 8px +
+
+
+
+
+ rounded-full +
+
+
+ +

Elevation

+
+
+
+
+ shadow-sm +
+
+
+
+
+ shadow-md +
+
+
+
+
+ shadow-lg +
+
+
+
+ + + +
+

Buttons & actions

+

+ Three action patterns cover the whole app: a filled primary button, + a bordered icon toggle (comparison tray), and an underlined text + action (dropdown menus and scope axes). +

+
+
+
+ +
+
+

Primary button

+

+ DownloadDataset.tsx · PublicationBlock.tsx · PathwaySearch.tsx +

+
+ bg-energyhover:bg-energy-700text-whiterounded-mdpx-4 py-2 +
+
+
+
+
+ +
+
+

Icon toggle — default / added / blocked

+

PathwayCard.tsx (comparison tray)

+
+ bg-bluespruceborder-neutral-200h-12 w-12 +
+

+ v2: the disabled look now covers two reasons, each with its + own label and tooltip — "Comparison full (max 3)" or "Cannot + add: no sector in common with your selection". A comparison + needs at least one shared sector. +

+
+
+
+
+ +
+
+

Text action

+

MultiSelectDropdown.tsx

+
+ underlinedisabled:opacity-50 +
+
+
+
+
+ +
+
+

Compact text action v2

+

ScopeAxisRow.tsx

+
+ text-[10px] underlinetext-rmigray-500hover:text-bluesprucefocus-visible:ring-bluespruce +
+
+
+
+
+ +
+

Badges

+

+ One component, sixteen semantic variants keyed to a + variant prop — plus a transparent "-pub" style used + when a value exists in a publication but has no interactive chart + data behind it. In v2 a badge can also be a control: pass + buttonProps and it renders as a + <button>. +

+
+
+ Default + Normative + 1.5°C + 2024 + Global + ASEAN + Vietnam + Power + Power generation + Emissions Intensity + Key Feature + Global · pub only + ASEAN · pub only + Vietnam · pub only + Power · pub only + Metric · pub only +
+
+

Badge variants

+

+ Badge.tsx — variant prop: default · pathwayType · temperature · + year · geographyGlobal/Region/Country(-pub) · sector(-pub) · + sectorSegment · metric(-pub) · keyFeature +

+
+ rounded-fullpx-2.5 py-0.5text-xs font-mediumborder +
+

+ v2: sectorSegment is new (bg-rmipink-100 text-rmipink-800 border-rmipink-200) — a subdivision of a sector such as "Power generation" or + "Energy storage", shown beside sector badges so it needs its own + color. The region and country "-pub" variants were fixed: they + referenced pinishgreen 500/600/700, which don't exist, and + rendered blank. Both now use + text-pinishgreen-800 border-pinishgreen-400. +

+
+
+
+
+
+ + + +
+
+
+

+ Selectable badge (toggle) v2 +

+

+ BadgeArray.tsx — selected + + onSelect props +

+
+ ring-2 ring-bluesprucering-offset-1 ring-offset-whitefont-semiboldaria-pressed +
+

+ Selection is shown with a ring and bolder text, never a color + change — color already says what kind of value the badge is. The + selected badge is moved to the front so it can't hide behind "+N + more". +

+
+
+
+
+
+ Vietnam + Thailand + Malaysia + +4 more +
+
+
+

Badge array — overflow

+

+ BadgeArray.tsx — measures container width, collapses to "+N + more" beyond maxRows +

+

+ v2: the "+N more" tooltip now uses the same display labels as + the visible badges (country names, not codes). +

+
+
+
+ +
+

Accordion item

+

+ Shared primitive behind the FAQ, five-step guide, and step-by-step + pages — a caret rotates and the panel toggles via the + hidden attribute rather than unmounting, so screen + readers keep a stable reference. +

+
+
+
+
+
+
What geographies are covered?
+ ▾ +
+
+
+

Collapsed

+
+
+
+
+
+
What geographies are covered?
+ ▾ +
+
+ Global, regional, and 40+ country-level pathways across + national plans, IEA scenarios, and NGFS models. +
+
+
+
+

Expanded

+

AccordionItem.tsx

+
+ aria-expandedaria-controlsrounded-full caret rotate-180 +
+
+
+
+
+ + + + + +
+

Numeric range slider

+

+ Used for the temperature filter — the track itself is a live + gradient across the warming bands, not a flat fill. +

+
+
+
+
+
+
+
+
+ 1.0°C1.5°C2.0°C2.5°C3.0°C3.5°C +
+
+
+
+

Temperature filter

+

NumericRangeSlider.tsx

+
+ bg-rmigray-200border-rmiblue-800ring-rmiblue-100 +
+
+
+
+ +
+

Sentiment & neutral scales

+

+ Ordinal-value bar charts used on pathway detail pages — bars run + unfavorable → favorable (sentiment) or are order-only with no + judgment implied (neutral). +

+
+
+
+
+
+
+
+
+
+
+
+
+ Moderate +
+
+

Sentiment scale (7-step)

+

SentimentScale.tsx

+
+
+
+
+
+
+
+
+
+
+ Sector coverage +
+
+

Neutral scale

+

NeutralScale.tsx

+
+
+
+
+ +
+

Tooltip & search highlight

+
+
+
+ Net Zero by 2050Model reaches net-zero CO₂ emissions across covered sectors + by this year. +
+
+

Tooltip

+

+ TextWithTooltip.tsx — portal-rendered to <body>, + positioned on hover/focus +

+

+ v2: the trigger can be a button (as="button", + cursor-pointer) when the tooltipped item is also + a control, such as a selectable badge. The default remains a + focusable span with cursor-help. +

+
+
+
+
+ IEA Net Zero Emissions Scenario +
+
+

Search match highlight

+

HighlightedText.tsx

+
+ bg-energy-100text-energy-800 +
+
+
+
+
+ +
+

Pathway card

+

+ The flagship composite — every token and component above assembles + here. This is the single highest-value component for a designer to + have pixel-accurate in Figma, since it repeats on every search + result. +

+
+
+
+
+
Normative Pathway
+
1.5°C
+
Net Zero By2050
+
+
+
IEA: Net Zero Emissions Scenario
+

+ A normative IEA scenario mapping a technically feasible path + to net-zero CO₂ emissions from the energy sector by 2050. +

+
+
Geographies
+
+ Global +
+
+
+
Sectors
+
+ Power + Transport + +3 more +
+
+
+
Benchmark metrics
+
+ Emissions Intensity +
+
+
+
+
Publication
+
World Energy Outlook 2024
+
+
+
Published
+
2024
+
+
+
+
+
View Details →
+
+
+
+
+
+
+

Pathway card

+

PathwayCard.tsx

+
+ shadow-md → hover:shadow-lgborder-neutral-200rounded-lg +
+
+
+
+ +
+

Header & footer

+
+
+
+
+
+
Transition Pathways Repository
+
Created by RMI
+
+ +
+
+
+

Header

+

Header.tsx

+
+ bg-bluesprucetext-whiteshadow-md +
+
+
+
+
+
+ © 2026 RMI. All rights reserved.Legal · Give Feedback +
+
+
+

Footer

+

Footer.tsx

+
+ bg-bluespruceborder-basalt +
+
+
+
+
+ + +
+

Tabs v2

+

+ The pathway detail page is now split into four tabs: At a glance, + Overview, Timeseries, and Scope & Granularity. The active tab is + saved in the URL (?tab=), so a link opens the same tab. + Arrow keys, Home, and End move between tabs. +

+
+
+
+ + + + +
+
+
+

Tab list — selected / resting / keyboard focus

+

Tabs.tsx — Tabs + TabPanel + useActiveTab

+
+ px-4 py-2.5 text-sm font-mediumborder-b-2selected: border-bluespruce text-bluespruceresting: text-rmigray-600hover:text-bluespruce hover:border-neutral-300focus-visible:ring-energy +
+
+
+
+ + +
+

Scope ribbon v2

+

+ A sticky bar under the page title that lets readers narrow the page + to one sector and one geography. It holds a row of selectable badges + per axis and, on the detail page, the tab list. Once the title + scrolls away, a slim Bluespruce bar slides in to restate it. +

+
+
+
+
+
+ IEA: Net Zero Emissions Scenario + Normative20501.5°C +
+
+
+ Sector +
+
+ + + + +2 more +
+
+ Geography +
+
+ + + + +12 more +
+
+
+ +
+
+
+
+

Detail page ribbon — scrolled state

+

PathwayContextRibbon.tsx · ScopeAxisRow.tsx

+
+ sticky top-0 z-20bg-white/95 backdrop-blurborder-b border-neutral-200condensed: h-10 bg-bluesprucegrid-cols-[auto_1fr] +
+

+ Labels use + text-[10px] font-semibold uppercase tracking-wider + text-rmigray-500. Each axis always has a value — there's no "clear" state. + The condensed bar's height animates in (200ms) and is skipped + for reduced motion. +

+
+
+ +
+
+
+
+ IEA NZEACE ATSNGFS Net Zero +
+
+
+ Sector +
+
+ +
+
+
+ Geography +
+ + + +
+

+ Publishers do not agree on what Southeast Asia covers: + only IEA NZE includes Timor-Leste. Figures below are not + exactly like-for-like. +

+
+
+
+
+

Comparison scope header

+

+ ComparisonScopeHeader.tsx · GeographyDivergenceNotice.tsx +

+
+ condensed: h-8 bg-bluespruce text-xs font-semiboldgrid-template-columns: repeat(n, 1fr)gap-x-6 +
+

+ One shared sector row for all columns. Geography gets one + dropdown per pathway, lined up under its column, because each + publisher names places differently. +

+
+
+ +
+
+ +
+
+
+ Global +
+
+ Southeast Asia +
+
+ Africa +
+
+ India +
+
+
+ Outlined geographies are listed in the publication but have + no chart data in this tool. +
+
+
+
+

Column geography select — open

+

ColumnGeographySelect.tsx

+
+ text-xs rounded-md border-gray-300selected: bg-neutral-100max-h-64 overflow-autoshadow-lg +
+

+ Options reuse the geography badges; outlined ("-pub") ones + have no chart data. The footnote wording here is illustrative + — the real text comes from + GEOGRAPHY_AVAILABILITY_TOOLTIP. +

+
+
+
+
+ + +
+

Data tables v2

+

+ Two new tables share one style: a Bluespruce header row, + zebra-striped body, and the first cell styled as a row heading. + Columns that would repeat the same value on every row (such as + Sector, once a sector is selected) are hidden automatically. +

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MetricSector segmentGranularityScope limitationsGeography coverageTime resolutionData format
Capacity +
+ Power generation +
+
By technologyUtility-scale only +
+ GlobalSoutheast AsiaAfrica+18 more +
+
5-yearExcel annex
Storage +
+ Energy storage +
+
UnspecifiedUnspecifiedUnspecifiedAnnualUnspecified
+
+
+
+

Data availability table

+

+ DataAvailabilityTable.tsx (Scope & Granularity tab) +

+
+ thead: bg-bluespruce text-white font-semiboldpx-3 py-2 text-smeven rows: bg-neutral-50row header: font-medium text-rmigray-800cells: text-rmigray-700rounded-lg border-neutral-200 +
+

+ Segment and geography cells render as badges (geography shows + 3, then "+N more"). Placeholder values — "Not covered", + "Unspecified", "No information" — stay as plain text, and a + "Not covered" row is plain text throughout. +

+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DependencyEvidence typeConstraint
Grid expansion + Quantitative + Transmission build-out must triple by 2035.
Critical minerals + Qualitative + + Assumes supply chains scale without price shocks. +
Public acceptance + Anecdotal + Siting of new capacity proceeds on schedule.
CCS availability + No evidence + Commercial-scale capture available from 2030.
+
+
+
+

Dependencies table + evidence pills

+

+ DependenciesTable.tsx (Overview tab) — EvidenceBadge, + strongest → weakest +

+
+ Quantitative: pinishgreen-200 / 800 / 800Qualitative: rmiblue-100 / 800 / 200Anecdotal: solar-100 / 800 / 200No evidence: neutral-100 / rmigray-600 / neutral-300 +
+

+ Pill colors listed as fill / text / border. The pill shape + matches Badge exactly but is its own component. Row content + above is sample text. +

+
+
+
+
+ + +
+

Section panels v2

+

+ The page's main content blocks now use one pattern: a light gray + panel with a title, holding white cards in a grid. Key features, + core drivers, coverage, and plots all follow it, so a tab reads as + one system. +

+
+
+
+
+

Assumptions & Trends Overview

+
+
+

Policies

+
+

Policy drivers

+

+ Carbon pricing rises steadily across advanced + economies, with phase-out dates for unabated coal. +

+
+
+

Policy types

+
+ Carbon pricePhaseout dates +
+
+
+
+

Emissions

+
+

Emissions target (driver)

+

+ Not a core driver for this pathway. +

+
+
+

Emissions scope

+
+ CO2e (Kyoto) +
+
+
+
+

Technology

+
+

Technology cost drivers

+

+ Learning rates for solar, wind, and batteries follow + historical trends. +

+
+
+
+
+
+
+

Panel with group cards — Key Features / core drivers

+

+ KeyFeatures.tsx · CoreDrivers.tsx · ComparisonKeyFeatures.tsx +

+
+ panel: bg-neutral-50 border-neutral-200 rounded-lg + p-4title: text-lg font-medium text-rmigray-800card: bg-white border rounded-lg p-4card title: text-xs font-semibold uppercase tracking-wide + text-rmigray-500grid gap-4 md:grid-cols-2 lg:grid-cols-3 +
+

+ Six groups in order: Policies, Emissions, Technology, + Investment, Energy System, Other. Each group shows its written + driver first, then its feature values. A driver marked not + applicable shows "Not a core driver for this pathway." in + italic text-rmigray-400. The At a glance tab + shows only the first three groups. +

+
+
+ +
+
+
+

Benchmark Plots

+
+
+
Capacity
+
chart
+
+ GlobalPower generation +
+
+
+
Emissions intensity
+
+ No data for this metric at Global. +
+
+ Global +
+
+
+
+
+
+

Plot small multiples

+

PlotGrid.tsx · PlotPanel.tsx

+
+ figure: bg-white border rounded-lg p-4figcaption: text-xs font-semibold uppercase + tracking-widegrid-cols-1 lg:grid-cols-2 gap-4empty: h-32 text-xs italic text-rmigray-400 +
+

+ One panel per plot type instead of one chart behind a + dropdown. Badges under each chart say which geography and + sector segment it shows. Metrics with no data anywhere are + left out; metrics missing only at this geography keep their + panel with an empty message (wording here is illustrative). +

+
+
+
+
+ + +
+

Notices & empty states v2

+

+ Plain-text messages that explain what a scope selection is hiding, + or why a section is empty. They appear only when something is + actually hidden or missing. Clearing a selection is always done in + the ribbon, never from these messages. +

+
+
+
+

+ Showing 3 of 8 rows for Power in Southeast Asia. +

+
+
+

Scope filter notice

+

ScopeFilterNotice.tsx

+
+ mb-4 text-sm text-rmigray-700 +
+
+
+
+
+

+ These columns are not showing the same geography: IEA NZE + shows Global and ACE ATS shows South East Asia. Figures below + are not like-for-like. +

+
+
+

Geography divergence notice

+

+ GeographyDivergenceNotice.tsx (comparison page) +

+
+ mt-2 text-xs text-rmigray-700 +
+
+
+
+
+
+

+ No dependencies have been recorded for this pathway yet. +

+
+
+
+

Empty state — nothing recorded

+

+ DependenciesTable.tsx · DataAvailabilityTable.tsx +

+
+ rounded-lg border-neutral-200 bg-neutral-50p-6 text-sm text-rmigray-600 +
+
+
+
+
+
+

No dependencies are recorded for Transport.

+

+ This pathway records 4 dependencies in other sectors — clear + the sector selection above to see them. +

+
+
+
+

Empty state — filtered out

+

+ Same shell, second line explains where the rows went +

+

+ Kept distinct from "nothing recorded" so the page never says a + pathway has no data when it's only hidden by a selection. +

+
+
+
+
+ + +
+

Using this in Figma

+

Two ways in, pick based on what you need first.

+ +

Option A — colors & type as Figma variables

+
    +
  1. + Install the Tokens Studio plugin (free) in Figma. +
  2. +
  3. + Copy the JSON block below into a new token set, or import it as a + file. +
  4. +
  5. + Use Tokens Studio's "Create Styles/Variables" action to turn every + entry into a real Figma color variable and text style, named + identically to the CSS tags above. +
  6. +
+
{
+  "color": {
+    "bluespruce": { "value": "#003b63", "type": "color" },
+    "energy": { "value": "#45cfcc", "type": "color" },
+    "energy-700": { "value": "#2ab5b2", "type": "color" },
+    "rmigray-800": { "value": "#231f20", "type": "color" }
+    /* … full token set: see the accompanying tpr-design-tokens.json */
+  },
+  "fontSize": { "sm": { "value": "14px" }, "base": { "value": "16px" } },
+  "fontWeight": { "medium": { "value": "500" }, "semibold": { "value": "600" } },
+  "borderRadius": { "md": { "value": "6px" }, "full": { "value": "9999px" } }
+}
+

+ The complete file — all 60+ colors, the type scale, weights, radius, + and shadow tokens — is tpr-design-tokens.json, in the + same folder as this page. Its colors are generated from + src/index.css automatically, so they always match the + app. +

+ +

+ Option B — component shapes as editable Figma layers +

+
    +
  1. + Install the html.to.design plugin in Figma. +
  2. +
  3. + Download this file (tpr-ui-kit.html) from the + repository, then drop it into the plugin's + File tab. +
  4. +
  5. + It converts the rendered badges, cards, buttons, and dropdowns + straight into native Figma frames, text, and fills — a real + starting point to refine rather than a picture to trace. +
  6. +
+ +
+ Keeping this from going stale: the colors in the + token file update automatically, but this page is maintained by + hand. When a component changes, update this page in the same pull + request, then refresh the Figma copy from it rather than editing + Figma directly — otherwise the two will quietly drift apart. See + docs/design/README.md for what is automatic and what is + manual. +
+
+
+
+ + diff --git a/package.json b/package.json index 62b2d87a..b8227758 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "schema:generate": "npm run schema:generate:types", "schema:generate:types": "npx ts-node --esm scripts/generate-types.ts && prettier --write \"src/types/**/*.d.ts\"", "test": "vitest run", - "test:watch": "vitest" + "test:watch": "vitest", + "tokens:build": "npx ts-node --esm scripts/build-design-tokens.ts && prettier --write \"docs/design/tpr-design-tokens.json\"" }, "author": "RMI", "license": "MIT", diff --git a/scripts/build-design-tokens.test.ts b/scripts/build-design-tokens.test.ts new file mode 100644 index 00000000..a10e89ff --- /dev/null +++ b/scripts/build-design-tokens.test.ts @@ -0,0 +1,80 @@ +import { describe, it, expect } from "vitest"; +import { buildTokens, parseThemeColors } from "./build-design-tokens.js"; + +describe("parseThemeColors", () => { + it("reads --color-* properties from @theme in source order", () => { + const css = ` + @import "tailwindcss"; + @theme { + /* Primary colours */ + --color-bluespruce: #003b63; + --color-energy:#45cfcc ; + --font-display: "Inter"; + } + `; + expect([...parseThemeColors(css)]).toEqual([ + ["bluespruce", "#003b63"], + ["energy", "#45cfcc"], + ]); + }); + + it("ignores colours outside @theme and inside comments", () => { + const css = ` + :root { --color-outside: #ffffff; } + @theme { + /* --color-commented: #000000; a { brace } too */ + --color-inside: #111111; + } + `; + expect([...parseThemeColors(css).keys()]).toEqual(["inside"]); + }); + + it("combines several @theme blocks, the last declaration winning", () => { + const css = ` + @theme { --color-a: #000001; --color-b: #000002; } + @theme { --color-a: #000003; } + `; + expect(Object.fromEntries(parseThemeColors(css))).toEqual({ + a: "#000003", + b: "#000002", + }); + }); + + it("fails loudly rather than emptying the token file", () => { + expect(() => parseThemeColors("body { color: red; }")).toThrow(/@theme/); + expect(() => parseThemeColors("@theme { --font-x: a; }")).toThrow( + /--color-/, + ); + }); +}); + +describe("buildTokens", () => { + const existing = { + color: { + energy: { + value: "#000000", + type: "color" as const, + description: "Accent.", + }, + removed: { value: "#ffffff", type: "color" as const }, + }, + fontSize: { sm: { value: "14px", type: "fontSizes" } }, + }; + + it("replaces colour values and keeps descriptions by name", () => { + const colors = new Map([ + ["bluespruce", "#003b63"], + ["energy", "#45cfcc"], + ]); + expect(buildTokens(colors, existing).color).toEqual({ + bluespruce: { value: "#003b63", type: "color" }, + energy: { value: "#45cfcc", type: "color", description: "Accent." }, + }); + }); + + it("leaves the hand-maintained groups and their order untouched", () => { + const tokens = buildTokens(new Map([["energy", "#45cfcc"]]), existing); + expect(Object.keys(tokens)).toEqual(["color", "fontSize"]); + expect(tokens.fontSize).toBe(existing.fontSize); + }); +}); diff --git a/scripts/build-design-tokens.ts b/scripts/build-design-tokens.ts new file mode 100644 index 00000000..7142fed5 --- /dev/null +++ b/scripts/build-design-tokens.ts @@ -0,0 +1,108 @@ +/** + * Regenerate the colour tokens in docs/design/tpr-design-tokens.json from the + * `@theme` block in src/index.css, so the designer's Figma tokens cannot drift + * from the colours the app actually renders. + * + * Run with `npm run tokens:build`. CI runs it too and fails when the committed + * file differs from the output (see node-build-committed-files.yml). + * + * Only the `color` group is generated. Everything else in the file -- the type + * scale, radius and shadows, which are Tailwind defaults src/index.css never + * defines -- is hand-maintained and carried over untouched, as is each colour's + * optional `description`. A renamed colour therefore loses its description. + */ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const CSS_PATH = path.resolve("src/index.css"); +const TOKENS_PATH = path.resolve("docs/design/tpr-design-tokens.json"); + +export interface ColorToken { + value: string; + type: "color"; + description?: string; +} + +type TokenFile = Record & { + color?: Record>; +}; + +/** + * Every `--color-*` custom property declared inside an `@theme` block, keyed by + * the name after `--color-`, in source order. + * + * Comments are stripped first so a brace or a commented-out declaration inside + * one cannot end the block early or leak in as a token. A name declared twice + * keeps its last value, as it would in CSS. + */ +export function parseThemeColors(css: string): Map { + const uncommented = css.replace(/\/\*[\s\S]*?\*\//g, ""); + const blocks = [...uncommented.matchAll(/@theme\b[^{]*\{([^}]*)\}/g)]; + if (blocks.length === 0) { + throw new Error("No @theme block found in the stylesheet."); + } + + const colors = new Map(); + for (const [, body] of blocks) { + for (const [, name, value] of body.matchAll( + /--color-([\w-]+)\s*:\s*([^;]+);/g, + )) { + colors.set(name, value.trim()); + } + } + if (colors.size === 0) { + throw new Error("The @theme block declares no --color-* properties."); + } + return colors; +} + +/** + * The token file with its `color` group rebuilt from `colors`. The group keeps + * its position among the file's top-level keys, and every other key is + * returned as it was. + */ +export function buildTokens( + colors: Map, + existing: TokenFile, +): TokenFile { + const previous = existing.color ?? {}; + const color: Record = {}; + for (const [name, value] of colors) { + const description = previous[name]?.description; + color[name] = description + ? { value, type: "color", description } + : { value, type: "color" }; + } + return { ...existing, color }; +} + +function main(): void { + if (!fs.existsSync(TOKENS_PATH)) { + // The file is the only home of the hand-maintained groups, so it is never + // created from scratch here. + throw new Error( + `${path.relative(process.cwd(), TOKENS_PATH)} is missing. Restore it from git before running this script.`, + ); + } + const css = fs.readFileSync(CSS_PATH, "utf8"); + const existing = JSON.parse( + fs.readFileSync(TOKENS_PATH, "utf8"), + ) as TokenFile; + + const colors = parseThemeColors(css); + const tokens = buildTokens(colors, existing); + fs.writeFileSync(TOKENS_PATH, `${JSON.stringify(tokens, null, 2)}\n`); + console.info( + `Wrote ${colors.size} colour tokens to ${path.relative(process.cwd(), TOKENS_PATH)}.`, + ); +} + +if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) { + try { + main(); + } catch (e: unknown) { + console.error(e instanceof Error ? e.message : e); + process.exit(1); + } +} From 23fee4406bc934ce2ef93a51cfde1c8edc0809a3 Mon Sep 17 00:00:00 2001 From: jacobvjk Date: Fri, 9 Oct 2026 11:53:15 +0200 Subject: [PATCH 2/3] =?UTF-8?q?fix(design):=20address=20review=20=E2=80=94?= =?UTF-8?q?=20tokens,=20card=20preview,=20JSON=20sample?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Tokens: add the missing 5xl line-height (48px) and give shadow-sm/md/lg both layers of Tailwind's composite shadows. - UI kit: rebuild the Pathway card preview from PathwayCard.tsx classes (padding, type sizes, label style, button layout). The 1.5°C temperature cell is pinishgreen-100 per getTemperatureColor, not rmired-100; same fix in the condensed scope ribbon. Default badge border is rmigray-200. - UI kit: the Tokens Studio sample is now valid JSON, and the step points at the real file instead. - UI kit: the intro no longer claims previews are exact; tags win. Co-Authored-By: Claude Opus 5.5 --- docs/design/tpr-design-tokens.json | 82 +++++-- docs/design/tpr-ui-kit.html | 349 ++++++++++++++++++++++------- 2 files changed, 323 insertions(+), 108 deletions(-) diff --git a/docs/design/tpr-design-tokens.json b/docs/design/tpr-design-tokens.json index 332d4c4c..b7abfa85 100644 --- a/docs/design/tpr-design-tokens.json +++ b/docs/design/tpr-design-tokens.json @@ -341,6 +341,10 @@ "4xl": { "value": "40px", "type": "lineHeights" + }, + "5xl": { + "value": "48px", + "type": "lineHeights" } }, "fontWeight": { @@ -381,36 +385,66 @@ }, "boxShadow": { "sm": { - "value": { - "x": "0", - "y": "1", - "blur": "3", - "spread": "0", - "color": "rgba(0,0,0,0.1)", - "type": "dropShadow" - }, + "value": [ + { + "x": "0", + "y": "1", + "blur": "3", + "spread": "0", + "color": "rgba(0,0,0,0.1)", + "type": "dropShadow" + }, + { + "x": "0", + "y": "1", + "blur": "2", + "spread": "-1", + "color": "rgba(0,0,0,0.1)", + "type": "dropShadow" + } + ], "type": "boxShadow" }, "md": { - "value": { - "x": "0", - "y": "4", - "blur": "6", - "spread": "-1", - "color": "rgba(0,0,0,0.1)", - "type": "dropShadow" - }, + "value": [ + { + "x": "0", + "y": "4", + "blur": "6", + "spread": "-1", + "color": "rgba(0,0,0,0.1)", + "type": "dropShadow" + }, + { + "x": "0", + "y": "2", + "blur": "4", + "spread": "-2", + "color": "rgba(0,0,0,0.1)", + "type": "dropShadow" + } + ], "type": "boxShadow" }, "lg": { - "value": { - "x": "0", - "y": "10", - "blur": "15", - "spread": "-3", - "color": "rgba(0,0,0,0.1)", - "type": "dropShadow" - }, + "value": [ + { + "x": "0", + "y": "10", + "blur": "15", + "spread": "-3", + "color": "rgba(0,0,0,0.1)", + "type": "dropShadow" + }, + { + "x": "0", + "y": "4", + "blur": "6", + "spread": "-4", + "color": "rgba(0,0,0,0.1)", + "type": "dropShadow" + } + ], "type": "boxShadow" } } diff --git a/docs/design/tpr-ui-kit.html b/docs/design/tpr-ui-kit.html index aba9301d..b0ae14ef 100644 --- a/docs/design/tpr-ui-kit.html +++ b/docs/design/tpr-ui-kit.html @@ -739,14 +739,18 @@ justify-content: space-between; } - /* pathway card */ + /* pathway card — each value mirrors a class in src/components/PathwayCard.tsx */ .p-card { - width: 300px; + width: 380px; /* fluid in the app: one cell of the results grid */ background: #fff; border-radius: 8px; border: 1px solid var(--color-neutral-200); - box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.08); + box-shadow: + 0 4px 6px -1px rgb(0 0 0 / 0.1), + 0 2px 4px -2px rgb(0 0 0 / 0.1); overflow: hidden; + display: flex; + flex-direction: column; } .p-card .strip { display: flex; @@ -754,100 +758,165 @@ } .p-card .strip .name { flex: 1; + min-width: 0; background: var(--color-neutral-100); - padding: 10px 16px; - font-size: 12.5px; + padding: 12px 20px; + display: flex; + align-items: center; + } + .p-card .strip .name span { + font-size: 14px; + line-height: 20px; font-weight: 500; color: var(--color-rmigray-700); text-transform: uppercase; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .p-card .strip .cells { + display: flex; + align-items: stretch; + height: 44px; } .p-card .strip .temp { - background: #eec7b7; - padding: 10px 14px; - font-size: 13px; - font-weight: 500; - color: var(--color-rmigray-700); + padding: 12px 20px; display: flex; align-items: center; + justify-content: center; + font-size: 14px; + line-height: 20px; + font-weight: 500; + color: var(--color-rmigray-700); } .p-card .strip .nz { + padding: 12px 20px; + display: flex; + align-items: center; background: var(--color-rmiblue-100); - padding: 6px 14px; - font-size: 13px; - font-weight: 500; - color: var(--color-rmigray-700); + min-width: 80px; + } + .p-card .strip .nz .stack { display: flex; flex-direction: column; align-items: center; - justify-content: center; } .p-card .strip .nz small { - font-size: 9.5px; + font-size: 10px; + line-height: 1.25; + height: 14px; + white-space: nowrap; color: var(--color-rmigray-600); } + .p-card .strip .nz .yr { + font-size: 14px; + line-height: 20px; + font-weight: 500; + color: var(--color-rmigray-700); + } .p-card .body { - padding: 18px; + padding: 20px; + display: flex; + flex-direction: column; + flex: 1; + } + .p-card .head { + margin-bottom: 16px; } .p-card h5 { - margin: 0 0 6px; - font-size: 17px; + margin: 0 0 8px; + font-size: 20px; + line-height: 28px; font-weight: 600; color: var(--color-bluespruce); } .p-card p.desc { - font-size: 12.5px; + margin: 0; + font-size: 14px; + line-height: 20px; color: var(--color-rmigray-600); - margin: 0 0 12px; - line-height: 1.45; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; } .p-card .field { - margin-bottom: 10px; + margin-bottom: 12px; } .p-card .field .lab { - font-size: 10.5px; - font-weight: 600; + margin: 0 0 4px; + font-size: 12px; + line-height: 16px; + font-weight: 500; color: var(--color-rmigray-500); - margin-bottom: 4px; - text-transform: uppercase; - letter-spacing: 0.02em; + display: flex; + align-items: center; + gap: 4px; + } + .p-card .field .lab svg { + color: var(--color-rmigray-400); } .p-card .badges { display: flex; flex-wrap: wrap; - gap: 5px; + } + .p-card .badges .badge { + margin: 0 8px 4px 0; /* Badge.tsx spaces badges with mr-2 mb-1 */ + } + .p-card .badges .more { + font-size: 12px; + color: var(--color-rmigray-500); + align-self: center; + white-space: nowrap; } .p-card .foot { - border-top: 1px solid #f1f1f1; - padding-top: 10px; - margin-top: 12px; + margin-top: auto; + padding-top: 12px; + border-top: 1px solid oklch(96.7% 0.003 264.542); /* Tailwind gray-100 */ + } + .p-card .foot .row { display: flex; justify-content: space-between; + align-items: center; + margin-bottom: 8px; } .p-card .foot .lab2 { - font-size: 10.5px; + margin: 0; + font-size: 12px; + line-height: 16px; color: var(--color-rmigray-500); } .p-card .foot .val { - font-size: 12.5px; + margin: 0; + font-size: 14px; + line-height: 20px; font-weight: 500; color: var(--color-rmigray-800); } .p-card .cta { display: flex; + gap: 8px; } .p-card .cta .view { flex: 1; + height: 48px; background: var(--color-rmiblue-100); + display: flex; + align-items: center; + justify-content: center; color: var(--color-bluespruce); - text-align: center; - padding: 12px; - font-size: 13px; + font-size: 16px; font-weight: 500; } + .p-card .cta .view svg { + margin-left: 8px; + } .p-card .cta .add { + height: 48px; width: 48px; + flex: none; background: #fff; - border-left: 1px solid var(--color-neutral-200); + border: 1px solid var(--color-neutral-200); display: flex; align-items: center; justify-content: center; @@ -1388,11 +1457,13 @@
Handoff

Pathways UI Kit

Every color, type size, and reusable interface component currently - shipping in the TPR front end, pulled directly from - src/index.css and src/components/ — not - redrawn from memory. Each entry carries the real Tailwind class - names and CSS custom properties, so a Figma file built from this - stays traceable back to the code that renders it. + shipping in the TPR front end, taken from + src/index.css and src/components/. Each + entry carries the real Tailwind class names and CSS custom + properties, so a Figma file built from this stays traceable back to + the code that renders it. Colors and class tags are exact; the + previews are hand-built and can drift from the code in small ways. + Where a preview and its tags disagree, the tags win.

Component previews sit on a fixed light stage regardless of the @@ -2668,7 +2739,7 @@

Badges

style=" --b: var(--color-rmigray-100); --t: var(--color-rmigray-800); - --bd: var(--color-rmigray-100); + --bd: var(--color-rmigray-200); " >Default @@ -3413,18 +3484,50 @@

Pathway card

-
Normative Pathway
-
1.5°C
-
Net Zero By2050
+
Normative Pathway
+
+
+ 1.5°C +
+
+
+ Net Zero By2050 +
+
+
-
IEA: Net Zero Emissions Scenario
-

- A normative IEA scenario mapping a technically feasible path - to net-zero CO₂ emissions from the energy sector by 2050. -

+
+
IEA: Net Zero Emissions Scenario
+

+ A normative IEA scenario mapping a technically feasible + path to net-zero CO₂ emissions from the energy sector by + 2050. +

+
-
Geographies
+

+ Geographies: + +

IEA: Net Zero Emissions Scenario
-
Sectors
+

+ Sectors: + +

IEA: Net Zero Emissions Scenario --bd: var(--color-solar-200); " >Power - Transport - +3 more + >Industry+2 more
-
Benchmark metrics
+

+ Benchmark Metrics: + +

IEA: Net Zero Emissions Scenario
-
-
Publication
-
World Energy Outlook 2024
+
+
+

Publication:

+

World Energy Outlook 2024

+
+
+

Published:

+

2024

+
-
-
Published
-
2024
+
+
+ View Details +
+
+ +
-
-
View Details →
-
+
-

Pathway card

PathwayCard.tsx

- shadow-md → hover:shadow-lgborder-neutral-200rounded-lg + rounded-lg border-neutral-200shadow-md → hover:shadow-lgstrip: px-5 py-3 text-sm uppercasebody: p-5title: text-xl font-semiboldlabels: text-xs font-mediumbuttons: h-12 gap-2
+

+ The temperature cell's color comes from + getTemperatureColor: ≤1.5°C pinishgreen-100, ≤2.0 + solar-100, ≤2.5 solar-200, ≤3.0 rmired-100, ≤3.5 rmired-200, + hotter rmired-400. The add-to-comparison button only appears + while the comparison tray is open. In the app the card's width + is set by the results grid (three columns on wide screens). +

@@ -3656,7 +3832,7 @@

Scope ribbon v2

>Normative20501.5°C @@ -4545,8 +4721,9 @@

Option A — colors & type as Figma variables

Install the Tokens Studio plugin (free) in Figma.
  • - Copy the JSON block below into a new token set, or import it as a - file. + Import tpr-design-tokens.json (in the same folder as + this page) as a token set. The block below is a short, valid + excerpt showing the format.
  • Use Tokens Studio's "Create Styles/Variables" action to turn every @@ -4563,11 +4740,15 @@

    Option A — colors & type as Figma variables

    "energy": { "value": "#45cfcc", "type": "color" }, "energy-700": { "value": "#2ab5b2", "type": "color" }, "rmigray-800": { "value": "#231f20", "type": "color" } - /* … full token set: see the accompanying tpr-design-tokens.json */ }, - "fontSize": { "sm": { "value": "14px" }, "base": { "value": "16px" } }, - "fontWeight": { "medium": { "value": "500" }, "semibold": { "value": "600" } }, - "borderRadius": { "md": { "value": "6px" }, "full": { "value": "9999px" } } + "fontSize": { + "sm": { "value": "14px", "type": "fontSizes" }, + "base": { "value": "16px", "type": "fontSizes" } + }, + "borderRadius": { + "md": { "value": "6px", "type": "borderRadius" }, + "full": { "value": "9999px", "type": "borderRadius" } + } }

    --- docs/design/tpr-ui-kit.html | 1014 ++++++++++++++++++++++++----------- 1 file changed, 699 insertions(+), 315 deletions(-) diff --git a/docs/design/tpr-ui-kit.html b/docs/design/tpr-ui-kit.html index b0ae14ef..c18594cd 100644 --- a/docs/design/tpr-ui-kit.html +++ b/docs/design/tpr-ui-kit.html @@ -428,24 +428,27 @@ font-style: italic; } - /* ---------- rendered component styles (mirrors real Tailwind classes 1:1) ---------- */ + /* ---------- rendered component styles: each value mirrors a class in the named source file ---------- */ + /* DownloadDataset.tsx, PublicationBlock.tsx, PathwaySearch.tsx */ .btn-primary { display: inline-flex; align-items: center; - gap: 8px; background: var(--color-energy); color: #fff; border-radius: 6px; padding: 8px 16px; - font-size: 14px; - font-weight: 500; + font-size: 16px; /* no text-* class: inherits the 16px base */ + line-height: 24px; + font-weight: 400; border: none; cursor: pointer; + font-family: inherit; } .btn-primary:hover { background: var(--color-energy-700); } + /* PathwayCard.tsx comparison button */ .btn-icon { height: 48px; width: 48px; @@ -470,26 +473,31 @@ cursor: not-allowed; } + /* MultiSelectDropdown.tsx: sits in the panel's text-xs header row, no colour of its own */ .btn-text { background: none; border: none; text-decoration: underline; - font-size: 14px; - color: var(--color-rmigray-700); + font-size: 12px; + line-height: 16px; + color: #000; cursor: pointer; padding: 0; + font-family: inherit; } .btn-text[disabled] { opacity: 0.5; cursor: not-allowed; } + /* Badge.tsx */ .badge { display: inline-flex; align-items: center; padding: 2px 10px; border-radius: 9999px; font-size: 12px; + line-height: 16px; font-weight: 500; border: 1px solid transparent; background: var(--b); @@ -497,91 +505,63 @@ border-color: var(--bd); } - .accordion-item { - width: 100%; - max-width: 420px; - border: 1px solid var(--color-neutral-200); - border-radius: 8px; - background: #fff; - } - .accordion-item .head { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 12px; - padding: 20px 24px; - cursor: pointer; - } - .accordion-item .head h5 { - margin: 0; - font-size: 15px; - font-weight: 600; - color: var(--color-rmigray-800); - } - .accordion-item .caret { - color: var(--color-rmigray-400); - transition: transform 0.2s; - } - .accordion-item.open .caret { - transform: rotate(180deg); - } - .accordion-item .panel { - padding: 0 24px 20px; - font-size: 13.5px; - color: var(--color-rmigray-600); - } - .accordion-item:not(.open) .panel { - display: none; - } - + /* SearchBox.tsx */ .search-box { display: flex; align-items: center; width: 100%; - max-width: 340px; + max-width: 420px; border-radius: 6px; border: 1px solid var(--color-neutral-300); background: #fff; } .search-box.focused { border-color: var(--color-energy-400); - box-shadow: 0 0 0 4px var(--color-energy-100); + box-shadow: 0 0 0 2px var(--color-energy-100); } - .search-box svg.lead { - margin-left: 12px; - color: #9aa2ab; + .search-box .lead { + padding-left: 12px; + color: oklch(70.7% 0.022 261.325); /* Tailwind gray-400 */ + display: flex; flex: none; } .search-box input { border: none; outline: none; padding: 12px; - font-size: 14px; + font-size: 16px; /* text-sm below the md breakpoint */ + line-height: 24px; width: 100%; color: var(--color-rmigray-700); background: transparent; + font-family: inherit; } .search-box .clear { background: none; border: none; - margin-right: 10px; + padding: 0 12px 0 0; + display: flex; color: var(--color-rmigray-400); cursor: pointer; } + /* DropdownFacetShell.tsx */ .facet-pill { display: inline-flex; align-items: center; justify-content: space-between; - gap: 10px; border-radius: 6px; padding: 8px 12px; - font-size: 13.5px; - box-shadow: 0 1px 2px rgb(0 0 0 / 0.05); - border: 1px solid var(--color-neutral-300); + font-size: 14px; + line-height: 20px; + box-shadow: + 0 1px 3px 0 rgb(0 0 0 / 0.1), + 0 1px 2px -1px rgb(0 0 0 / 0.1); + border: 1px solid oklch(87.2% 0.01 258.338); /* Tailwind gray-300 */ background: #fff; color: var(--color-rmigray-800); min-width: 128px; + text-align: left; cursor: default; } .facet-pill.active { @@ -592,39 +572,60 @@ .facet-pill strong { font-weight: 700; } + .facet-pill .icon { + margin-left: 8px; + height: 20px; + width: 20px; + display: inline-flex; + align-items: center; + justify-content: center; + flex: none; + } + /* DropdownFacetShell.tsx panel + MultiSelectDropdown.tsx list */ .facet-panel { - border: 1px solid var(--color-neutral-200); + border: 1px solid oklch(92.8% 0.006 264.531); /* Tailwind gray-200 */ border-radius: 6px; background: #fff; - box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1); - width: 220px; - overflow: hidden; + box-shadow: + 0 10px 15px -3px rgb(0 0 0 / 0.1), + 0 4px 6px -4px rgb(0 0 0 / 0.1); + min-width: 160px; + margin-top: 8px; } .facet-panel .fp-head { display: flex; + align-items: flex-start; justify-content: space-between; - padding: 10px 12px 8px; + gap: 12px; + padding: 0 8px 8px; font-size: 12px; - } - .facet-panel .fp-head a { - color: var(--color-rmigray-800); - text-decoration: underline; + line-height: 16px; } .facet-panel ul { list-style: none; margin: 0; - padding: 4px 8px 10px; + padding: 8px; } .facet-panel li { display: flex; align-items: center; gap: 8px; - padding: 5px 4px; - font-size: 13.5px; - color: var(--color-rmigray-800); + padding: 4px 8px; + font-size: 14px; + line-height: 20px; + color: #000; + } + .facet-panel .fp-foot { + border-top: 1px solid oklch(92.8% 0.006 264.531); + padding: 8px; + font-size: 12px; + line-height: 16px; + font-style: italic; + color: var(--color-rmigray-500); } + /* SentimentScale.tsx, NeutralScale.tsx */ .scale-row { display: flex; gap: 2px; @@ -634,27 +635,38 @@ .scale-row .bar { flex: 1; height: 6px; - border-radius: 2px; + border-radius: 4px; } .scale-row .bar.sel { height: 12px; } + .scale-label { + margin-top: 6px; + font-size: 12px; + line-height: 16px; + font-weight: 600; + } + /* NumericRangeSlider.tsx */ .num-slider { - width: 300px; + width: 320px; + padding-bottom: 20px; } .num-track { position: relative; height: 8px; border-radius: 4px; - background: linear-gradient( - to right, - #2888c9 0%, - #77b8e4 20%, - #91cbf2 33%, - #df4e39 55%, - #ab3c2c 100% - ); + } + .num-track .fill { + position: absolute; + inset: 0; + border-radius: 4px; + } + .num-track .cover { + position: absolute; + top: 0; + height: 8px; + background: rgb(230 236 239); /* hard-coded in the component */ } .num-track .handle { position: absolute; @@ -663,50 +675,83 @@ width: 16px; border-radius: 9999px; background: #fff; - border: 2px solid var(--color-rmiblue-800); - box-shadow: 0 0 0 3px var(--color-rmiblue-100); + border: 1px solid var(--color-rmiblue-800); + box-shadow: + 0 0 0 2px var(--color-rmiblue-100), + 0 4px 6px -1px rgb(0 0 0 / 0.1), + 0 2px 4px -2px rgb(0 0 0 / 0.1); } - .num-ticks { - display: flex; - justify-content: space-between; - margin-top: 10px; - font-size: 10px; + .num-track .tick { + position: absolute; + bottom: -20px; + transform: translateX(-50%); + font-size: 12px; + line-height: 16px; color: var(--color-rmigray-500); - font-family: var(--font-mono); + white-space: nowrap; } + /* TextWithTooltip.tsx */ .tooltip-demo { position: relative; display: inline-flex; + align-items: center; + gap: 4px; + font-size: 12px; + font-weight: 500; + color: var(--color-rmigray-500); + } + .tooltip-demo .trigger { + display: inline-flex; + color: var(--color-rmigray-400); + cursor: help; } .tooltip-demo .bubble { position: absolute; - left: calc(100% + 10px); + left: calc(100% + 8px); top: 50%; transform: translateY(-50%); background: #fff; border: 1px solid var(--color-rmigray-100); color: var(--color-rmigray-500); font-size: 12px; + line-height: 16px; + font-weight: 400; padding: 8px 12px; - border-radius: 6px; - box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1); - width: 180px; + border-radius: 4px; + box-shadow: + 0 10px 15px -3px rgb(0 0 0 / 0.1), + 0 4px 6px -4px rgb(0 0 0 / 0.1); + width: 260px; /* max-w-xs (320px) in the app */ + opacity: 0.95; + } + .tooltip-demo .bubble::before { + content: ""; + position: absolute; + left: -8px; + top: 50%; + transform: translateY(-50%); + border: 4px solid transparent; + border-right-color: var(--color-rmigray-100); } + /* HighlightedText.tsx */ .mark-demo mark { background: var(--color-energy-100); color: var(--color-energy-800); padding: 0 2px; - border-radius: 2px; + border-radius: 4px; font-weight: 500; } + /* Header.tsx, HeaderNav.tsx, ResourcesDropdown.tsx */ .chrome-bar { background: var(--color-bluespruce); color: #fff; - border-radius: 8px; - padding: 14px 20px; + box-shadow: + 0 4px 6px -1px rgb(0 0 0 / 0.1), + 0 2px 4px -2px rgb(0 0 0 / 0.1); + padding: 16px; width: 100%; display: flex; align-items: center; @@ -714,29 +759,92 @@ } .chrome-bar .brand { font-weight: 700; - font-size: 15px; + font-size: 24px; + line-height: 32px; + letter-spacing: -0.025em; } .chrome-bar .sub { - font-size: 10.5px; - color: rgba(255, 255, 255, 0.75); - margin-top: 2px; + margin-top: 4px; + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + line-height: 20px; + color: rgb(255 255 255 / 0.8); + } + .chrome-bar .logo { + font-weight: 800; + font-size: 15px; + letter-spacing: 0.04em; + color: #fff; + border: 1px dashed rgb(255 255 255 / 0.5); + padding: 0 4px; + line-height: 18px; } .chrome-bar nav { display: flex; - gap: 16px; - font-size: 12.5px; + gap: 8px; + } + .chrome-bar nav span { + display: inline-flex; + align-items: center; + gap: 8px; + border-radius: 6px; + padding: 8px 12px; + font-size: 14px; + line-height: 20px; + font-weight: 600; + color: rgb(255 255 255 / 0.9); } + /* Footer.tsx */ .foot-chrome { background: var(--color-bluespruce); border-top: 1px solid var(--color-basalt); - border-radius: 8px; - padding: 16px 20px; + padding: 32px 16px; width: 100%; color: #fff; - font-size: 12px; + } + .foot-chrome .row { display: flex; justify-content: space-between; + align-items: center; + } + .foot-chrome .left { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + line-height: 20px; + font-weight: 500; + } + .foot-chrome .links { + display: flex; + align-items: center; + gap: 24px; + font-size: 14px; + line-height: 20px; + } + .foot-chrome .links span { + display: inline-flex; + align-items: center; + gap: 4px; + } + .foot-chrome .copy { + margin-top: 16px; + padding-top: 16px; + border-top: 1px solid var(--color-basalt); + text-align: center; + font-size: 12px; + line-height: 16px; + } + .foot-chrome .logo { + font-weight: 800; + font-size: 15px; + letter-spacing: 0.04em; + border: 1px dashed rgb(255 255 255 / 0.5); + padding: 0 4px; + line-height: 22px; } /* pathway card — each value mirrors a class in src/components/PathwayCard.tsx */ @@ -1084,10 +1192,16 @@ .ribbon .axes .vals { display: flex; flex-wrap: wrap; - gap: 6px; + gap: 0; padding: 2px 0; align-items: center; } + .ribbon .axes .vals .badge, + .plot-fig .under .badge, + .geo-list .opt .badge, + table.data-table .badges .badge { + margin: 0 8px 4px 0; /* Badge.tsx: mr-2 mb-1 */ + } .ribbon .geo-row { padding: 8px 24px 12px; } @@ -1101,23 +1215,25 @@ border-radius: 0; padding: 8px 24px 0; border-bottom: none; - border-top: 1px solid var(--color-neutral-200); - margin-top: 8px; + background: transparent; } .geo-select { + /* ColumnGeographySelect.tsx */ display: flex; width: 100%; align-items: center; justify-content: space-between; gap: 8px; border-radius: 6px; - border: 1px solid var(--color-neutral-300); + border: 1px solid oklch(87.2% 0.01 258.338); background: #fff; padding: 4px 8px; font-size: 12px; color: var(--color-rmigray-800); - box-shadow: 0 1px 2px rgb(0 0 0 / 0.05); + box-shadow: + 0 1px 3px 0 rgb(0 0 0 / 0.1), + 0 1px 2px -1px rgb(0 0 0 / 0.1); font-family: inherit; text-align: left; } @@ -1133,10 +1249,12 @@ } .geo-list { width: 240px; - border: 1px solid var(--color-neutral-200); + border: 1px solid oklch(92.8% 0.006 264.531); border-radius: 6px; background: #fff; - box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1); + box-shadow: + 0 10px 15px -3px rgb(0 0 0 / 0.1), + 0 4px 6px -4px rgb(0 0 0 / 0.1); margin-top: 4px; } .geo-list .opts { @@ -1151,7 +1269,7 @@ background: var(--color-neutral-100); } .geo-list .foot { - border-top: 1px solid var(--color-neutral-200); + border-top: 1px solid oklch(92.8% 0.006 264.531); padding: 8px; font-size: 12px; font-style: italic; @@ -1187,11 +1305,13 @@ table.data-table tbody tr:nth-child(even) { background: var(--color-neutral-50); } + table.data-table.deps tbody th { + white-space: nowrap; + } table.data-table tbody th { text-align: left; font-weight: 500; color: var(--color-rmigray-800); - white-space: nowrap; } table.data-table tbody td { color: var(--color-rmigray-700); @@ -1199,7 +1319,7 @@ table.data-table .badges { display: flex; flex-wrap: wrap; - gap: 4px; + gap: 0; } .empty-state { @@ -1314,7 +1434,7 @@ font-family: var(--font-mono); } .plot-fig .chart-empty { - height: 110px; + height: 128px; display: flex; align-items: center; justify-content: center; @@ -1328,7 +1448,7 @@ margin-top: 4px; display: flex; flex-wrap: wrap; - gap: 4px; + gap: 0; } @media (max-width: 1000px) { @@ -1423,7 +1543,6 @@

    Reference
    Components
    Buttons Badges - Accordion Search box Filter dropdown Range slider @@ -2600,7 +2719,7 @@

    Buttons & actions

    - +

    Primary button

    @@ -2612,7 +2731,8 @@

    Primary button

    >hover:bg-energy-700text-whiterounded-mdpx-4 py-2 + >px-4 py-2no text size: 16px base
    @@ -3033,61 +3153,6 @@

    Badge array — overflow

  • -
    -

    Accordion item

    -

    - Shared primitive behind the FAQ, five-step guide, and step-by-step - pages — a caret rotates and the panel toggles via the - hidden attribute rather than unmounting, so screen - readers keep a stable reference. -

    -
    -
    -
    -
    -
    -
    What geographies are covered?
    - ▾ -
    -
    -
    -

    Collapsed

    -
    -
    -
    -
    -
    -
    What geographies are covered?
    - ▾ -
    -
    - Global, regional, and 40+ country-level pathways across - national plans, IEA scenarios, and NGFS models. -
    -
    -
    -
    -

    Expanded

    -

    AccordionItem.tsx

    -
    - aria-expandedaria-controlsrounded-full caret rotate-180 -
    -
    -
    -
    -
    - @@ -3315,22 +3491,19 @@

    Sentiment & neutral scales

    Ordinal-value bar charts used on pathway detail pages — bars run unfavorable → favorable (sentiment) or are order-only with no - judgment implied (neutral). + judgment implied (neutral). The selected bar is taller; the label + below names it.

    Sentiment & neutral scales style="background: var(--color-rmired-100); opacity: 0.2" >
    Sentiment & neutral scales >
    ModerateModerate improvement

    Sentiment scale (7-step)

    SentimentScale.tsx

    +
    + bars: h-1.5 rounded-sm gap-0.5selected: h-3others: opacity-20label: text-xs font-semibold, bar's color +
    +

    + Palette runs rmired-400 → rmired-200 → rmired-100 → + neutral-500 → success-400 → success-600 → success-800, + reversed when the first value is the good one. Three-step + scales use rmired-400, neutral-500, success-600. "No + information" shows the default badge instead of a label. +

    +
    +
    +
    Sentiment scale (7-step) >
    Sector coverageCurrent/legislated policies

    Neutral scale

    NeutralScale.tsx

    +
    + bars: bg-neutral-200selected: h-3 bg-rmiblue-400label: text-xs font-semibold text-rmiblue-400 +
    @@ -3422,18 +3619,33 @@

    Neutral scale

    Tooltip & search highlight

    -
    +
    Net Zero by 2050Geographies: + Model reaches net-zero CO₂ emissions across covered sectors - by this year.Filled badges indicate that data is available for + visualization and download in this tool. Outlined badges + indicate the data for this geography is available in the + source publication, but not currently in this tool.
    @@ -3443,6 +3655,12 @@

    Tooltip

    TextWithTooltip.tsx — portal-rendered to <body>, positioned on hover/focus

    +
    + bg-white border-rmigray-100 roundedtext-xs text-rmigray-500 px-3 py-2shadow-lg max-w-xs opacity-95arrow: border-4 +

    v2: the trigger can be a button (as="button", cursor-pointer) when the tooltipped item is also @@ -3462,7 +3680,8 @@

    Search match highlight

    HighlightedText.tsx

    bg-energy-100text-energy-800 + >text-energy-800px-0.5 rounded-sm font-medium
    @@ -3692,8 +3911,12 @@

    Pathway card

    id="chrome" >

    Header & footer

    +

    + Both use the RMI logo image (/RMILogo-white.svg); the + dashed "RMI" boxes below stand in for it. +

    -
    +
    Header & footer
    Transition Pathways Repository
    -
    Created by RMI
    +
    + Created by +

    Header

    -

    Header.tsx

    +

    + Header.tsx · HeaderNav.tsx · ResourcesDropdown.tsx +

    - bg-bluesprucetext-whiteshadow-md + bg-bluespruce text-white shadow-mdpx-4 py-4title: text-xl md:text-2xl font-bold tracking-tight"Created by": text-xs md:text-sm text-white/80nav: px-3 py-2 text-sm font-semibold text-white/90
    +

    + Shown at desktop size. On narrow screens the nav stacks under + the title. +

    -
    +
    - © 2026 RMI. All rights reserved.Legal · Give Feedback +
    +
    + Transition Pathways + Repository +
    + +
    +
    © 2026 RMI. All rights reserved.

    Footer

    Footer.tsx

    - bg-bluespruceborder-basalt + bg-bluespruce border-t border-basaltpx-4 py-8text-sm text-white hover:text-energycopyright: text-xs, border-t border-basalt mt-4 pt-4
    -
    Comparison scope header
    - Outlined geographies are listed in the publication but have - no chart data in this tool. + Filled badges indicate that data is available for + visualization and download in this tool. Outlined badges + indicate the data for this geography is available in the + source publication, but not currently in this tool.
    @@ -4153,9 +4426,9 @@

    Column geography select — open

    Options reuse the geography badges; outlined ("-pub") ones - have no chart data. The footnote wording here is illustrative - — the real text comes from - GEOGRAPHY_AVAILABILITY_TOOLTIP. + have no chart data. The footnote is + GEOGRAPHY_AVAILABILITY_TOOLTIP, the same text as + the Geographies tooltip on the pathway card.

    @@ -4307,7 +4580,7 @@

    Data availability table

    style="width: 100%" >
    - +
    @@ -4443,23 +4716,79 @@

    Policies

    Policy types

    -
    +
    Carbon priceFeed-in tariffsPerformance standardsPhaseout datesSubsidiesTarget technology sharesOtherNone
    @@ -4474,15 +4803,61 @@

    Emissions

    Emissions scope

    -
    +
    No informationCO2CO2e (Kyoto)CO2e (CO2, Methane)CO2e (unspecified GHGs)Other emissions scope
    @@ -4517,6 +4892,12 @@

    Panel with group cards — Key Features / core drivers

    text-rmigray-500grid gap-4 md:grid-cols-2 lg:grid-cols-3chosen value: bg-rmiblue-100 text-rmiblue-800 + border-rmiblue-200other options: bg-neutral-50 text-neutral-400 + border-neutral-200

    @@ -4525,7 +4906,8 @@

    Panel with group cards — Key Features / core drivers

    driver first, then its feature values. A driver marked not applicable shows "Not a core driver for this pathway." in italic text-rmigray-400. The At a glance tab - shows only the first three groups. + shows only the first three groups. Pick-list features show + every option, with the chosen ones in blue and the rest faded.

    @@ -4570,7 +4952,9 @@

    Benchmark Plots

    Emissions intensity
    - No data for this metric at Global. + For this pathway, there is currently no data available + for the selected combination of sector, region, and + metric.
    Plot small multiples dropdown. Badges under each chart say which geography and sector segment it shows. Metrics with no data anywhere are left out; metrics missing only at this geography keep their - panel with an empty message (wording here is illustrative). + panel with an empty message.

    Dependency