diff --git a/website/next.config.ts b/website/next.config.ts
index 0b1135045..6d6492bed 100644
--- a/website/next.config.ts
+++ b/website/next.config.ts
@@ -9,6 +9,11 @@ const nextConfig: NextConfig = {
destination: "/us",
permanent: false,
},
+ {
+ source: "/tools",
+ destination: "/us/tools",
+ permanent: false,
+ },
];
},
};
diff --git a/website/src/__tests__/pages/static-pages.test.tsx b/website/src/__tests__/pages/static-pages.test.tsx
index 2b97b5d48..3a952215c 100644
--- a/website/src/__tests__/pages/static-pages.test.tsx
+++ b/website/src/__tests__/pages/static-pages.test.tsx
@@ -5,6 +5,7 @@ import PrivacyPage from "../../app/[countryId]/privacy/page";
import TermsPage from "../../app/[countryId]/terms/page";
import ResearchPage from "../../app/[countryId]/research/page";
import ClaudePluginPage from "../../app/[countryId]/claude-plugin/page";
+import ToolsPage from "../../app/[countryId]/tools/page";
describe("static pages", () => {
test("Donate page renders heading", async () => {
@@ -49,4 +50,12 @@ describe("static pages", () => {
expect(el).toBeTruthy();
expect(el.type).toBeDefined();
});
+
+ test("Tools page returns a valid element", async () => {
+ const el = await ToolsPage({
+ params: Promise.resolve({ countryId: "us" }),
+ });
+ expect(el).toBeTruthy();
+ expect(el.type).toBeDefined();
+ });
});
diff --git a/website/src/app/[countryId]/brand/writing/page.tsx b/website/src/app/[countryId]/brand/writing/page.tsx
index 8f504ca58..edf45206b 100644
--- a/website/src/app/[countryId]/brand/writing/page.tsx
+++ b/website/src/app/[countryId]/brand/writing/page.tsx
@@ -372,7 +372,7 @@ export default function BrandWritingPage() {
>
PolicyEngine's voice is research-oriented but accessible. We
explain complex policy concepts clearly while maintaining rigor.
- When writing about our products, the tone can be more natural and
+ When writing about our tools, the tone can be more natural and
conversational.
diff --git a/website/src/app/[countryId]/page.tsx b/website/src/app/[countryId]/page.tsx
index 957cd79c6..b987d021f 100644
--- a/website/src/app/[countryId]/page.tsx
+++ b/website/src/app/[countryId]/page.tsx
@@ -1,5 +1,6 @@
import HeroSection from "@/components/home/HeroSection";
import HomeBlogPreview from "@/components/home/HomeBlogPreview";
+import HomeToolsPreview from "@/components/home/HomeToolsPreview";
import HomeTrackerPreview from "@/components/home/HomeTrackerPreview";
import OrgLogos from "@/components/home/OrgLogos";
import FeaturedResearchBanner from "@/components/home/FeaturedResearchBanner";
@@ -17,6 +18,7 @@ export default async function HomePage({
+
diff --git a/website/src/app/[countryId]/tools/page.tsx b/website/src/app/[countryId]/tools/page.tsx
new file mode 100644
index 000000000..75c77b496
--- /dev/null
+++ b/website/src/app/[countryId]/tools/page.tsx
@@ -0,0 +1,24 @@
+import type { Metadata } from "next";
+import ToolsShowcase from "@/components/tools/ToolsShowcase";
+import { getToolsForCountry } from "@/data/tools";
+
+export const metadata: Metadata = {
+ title: "Tools",
+ description:
+ "Interactive PolicyEngine tools, calculators, and developer tooling.",
+};
+
+export default async function ToolsPage({
+ params,
+}: {
+ params: Promise<{ countryId: string }>;
+}) {
+ const { countryId } = await params;
+
+ return (
+
+ );
+}
diff --git a/website/src/components/Header.tsx b/website/src/components/Header.tsx
index ee3a87a84..fc7070a98 100644
--- a/website/src/components/Header.tsx
+++ b/website/src/components/Header.tsx
@@ -561,6 +561,11 @@ export default function Header() {
const navItems: NavItemSetup[] = [
{ label: "Research", href: `/${countryId}/research`, hasDropdown: false },
+ { label: "Tools", href: `/${countryId}/tools`, hasDropdown: false },
+ { label: "Model", href: `/${countryId}/model`, hasDropdown: false },
+ ...(countryId === "us"
+ ? [{ label: "API", href: `/${countryId}/api`, hasDropdown: false }]
+ : []),
{
label: "About",
hasDropdown: true,
diff --git a/website/src/components/home/HomeToolsPreview.tsx b/website/src/components/home/HomeToolsPreview.tsx
new file mode 100644
index 000000000..388d50896
--- /dev/null
+++ b/website/src/components/home/HomeToolsPreview.tsx
@@ -0,0 +1,211 @@
+import Link from "next/link";
+import {
+ colors,
+ spacing,
+ typography,
+} from "@policyengine/design-system/tokens";
+import { getToolsForCountry } from "@/data/tools";
+
+function ActionLink({
+ href,
+ label,
+ external = false,
+}: {
+ href: string;
+ label: string;
+ external?: boolean;
+}) {
+ const style: React.CSSProperties = {
+ display: "inline-flex",
+ alignItems: "center",
+ gap: "8px",
+ textDecoration: "none",
+ color: colors.primary[700],
+ fontWeight: typography.fontWeight.semibold,
+ fontSize: typography.fontSize.sm,
+ fontFamily: typography.fontFamily.primary,
+ };
+
+ if (external) {
+ return (
+
+ {label} →
+
+ );
+ }
+
+ return (
+
+ {label} →
+
+ );
+}
+
+export default function HomeToolsPreview({
+ countryId,
+}: {
+ countryId: string;
+}) {
+ const tools = getToolsForCountry(countryId).slice(0, 3);
+
+ if (tools.length === 0) return null;
+
+ return (
+
+
+
+
+
+ Tools
+
+
+ Open tools, not just articles.
+
+
+ Explore calculators, developer tools, and analysis tools built
+ for real use cases.
+
+
+
+ View all tools →
+
+
+
+
+ {tools.map((tool) => (
+
+
+ {tool.kind}
+
+
+
+ {tool.title}
+
+
+
+ {tool.summary}
+
+
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/website/src/components/tools/ToolsShowcase.tsx b/website/src/components/tools/ToolsShowcase.tsx
new file mode 100644
index 000000000..e98d1f31d
--- /dev/null
+++ b/website/src/components/tools/ToolsShowcase.tsx
@@ -0,0 +1,1006 @@
+"use client";
+
+import { useEffect, useMemo, useRef, useState } from "react";
+import Link from "next/link";
+import { IconArrowRight } from "@tabler/icons-react";
+import {
+ colors,
+ typography,
+} from "@policyengine/design-system/tokens";
+import {
+ CATEGORY_DESCRIPTIONS,
+ type ToolCategory,
+ type ToolDefinition,
+ type ToolTone,
+} from "@/data/tools";
+
+function useInView(threshold = 0.12) {
+ const ref = useRef
(null);
+ const [visible, setVisible] = useState(false);
+
+ useEffect(() => {
+ const element = ref.current;
+ if (!element) return;
+
+ const observer = new IntersectionObserver(
+ ([entry]) => {
+ if (entry.isIntersecting) {
+ setVisible(true);
+ observer.disconnect();
+ }
+ },
+ { threshold },
+ );
+
+ observer.observe(element);
+ return () => observer.disconnect();
+ }, [threshold]);
+
+ return { ref, visible };
+}
+
+function Reveal({
+ children,
+ delay = 0,
+ style,
+}: {
+ children: React.ReactNode;
+ delay?: number;
+ style?: React.CSSProperties;
+}) {
+ const { ref, visible } = useInView();
+
+ return (
+
+ {children}
+
+ );
+}
+
+const SECTION_X: React.CSSProperties = {
+ paddingLeft: "6.125%",
+ paddingRight: "6.125%",
+};
+
+const CONTAINER: React.CSSProperties = {
+ maxWidth: 1240,
+ marginLeft: "auto",
+ marginRight: "auto",
+};
+
+const toneStyles: Record<
+ ToolTone,
+ {
+ background: string;
+ border: string;
+ glow: string;
+ }
+> = {
+ teal: {
+ background:
+ "linear-gradient(135deg, rgba(230,255,250,0.98), rgba(203,250,245,0.92))",
+ border: "rgba(56, 178, 172, 0.28)",
+ glow: "rgba(49, 151, 149, 0.12)",
+ },
+ slate: {
+ background:
+ "linear-gradient(135deg, rgba(15,23,42,0.98), rgba(30,41,59,0.96))",
+ border: "rgba(148, 163, 184, 0.28)",
+ glow: "rgba(15, 23, 42, 0.22)",
+ },
+ amber: {
+ background:
+ "linear-gradient(135deg, rgba(255,251,235,0.98), rgba(254,243,199,0.95))",
+ border: "rgba(217, 119, 6, 0.22)",
+ glow: "rgba(245, 158, 11, 0.12)",
+ },
+ rose: {
+ background:
+ "linear-gradient(135deg, rgba(255,241,242,0.98), rgba(255,228,230,0.94))",
+ border: "rgba(225, 29, 72, 0.18)",
+ glow: "rgba(225, 29, 72, 0.1)",
+ },
+ sky: {
+ background:
+ "linear-gradient(135deg, rgba(240,249,255,0.98), rgba(224,242,254,0.94))",
+ border: "rgba(2, 132, 199, 0.18)",
+ glow: "rgba(2, 132, 199, 0.12)",
+ },
+};
+
+const countryLabels: Record = {
+ us: "United States",
+ uk: "United Kingdom",
+ ca: "Canada",
+ ng: "Nigeria",
+ il: "Israel",
+};
+
+const categoryOrder: ToolCategory[] = [
+ "Policy calculators",
+ "Developer tools",
+ "Emulators and analysis tools",
+];
+
+const terminalStyles = {
+ comment: { prefix: "", color: "#6B7280", prefixColor: "#6B7280" },
+ command: { prefix: "$ ", color: "#E5E7EB", prefixColor: "#7DD3FC" },
+ prompt: { prefix: "> ", color: "#F8FAFC", prefixColor: "#5EEAD4" },
+ output: { prefix: " ", color: "#CBD5E1", prefixColor: "#CBD5E1" },
+ success: { prefix: " ", color: "#86EFAC", prefixColor: "#86EFAC" },
+} as const;
+
+function ActionLink({
+ action,
+}: {
+ action: { label: string; href: string; external?: boolean };
+}) {
+ const sharedStyle: React.CSSProperties = {
+ display: "inline-flex",
+ alignItems: "center",
+ gap: "8px",
+ padding: "13px 19px",
+ borderRadius: "999px",
+ textDecoration: "none",
+ fontFamily: typography.fontFamily.primary,
+ fontSize: typography.fontSize.sm,
+ fontWeight: typography.fontWeight.semibold,
+ transition: "transform 0.18s ease, box-shadow 0.18s ease, background-color 0.18s ease",
+ boxShadow: "0 18px 40px -24px rgba(35,78,82,0.35)",
+ backdropFilter: "blur(18px)",
+ WebkitBackdropFilter: "blur(18px)",
+ };
+
+ if (action.external) {
+ return (
+
+ {action.label}
+
+
+ );
+ }
+
+ return (
+
+ {action.label}
+
+
+ );
+}
+
+function ToolPreviewPanel({ tool }: { tool: ToolDefinition }) {
+ if (tool.preview.type === "image") {
+ return (
+
+
+ {/* eslint-disable-next-line @next/next/no-img-element */}
+
+
+ );
+ }
+
+ if (tool.preview.type === "metrics") {
+ return (
+
+
+
+ {tool.preview.eyebrow}
+
+
+ {tool.title}
+
+
+
+ {tool.preview.items.map((item) => (
+
+
+ {item.label}
+
+
+ {item.value}
+
+
+ ))}
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+ {tool.preview.lines.map((line, index) => {
+ const style = terminalStyles[line.kind];
+ return (
+
+ {style.prefix}
+ {line.text}
+
+ );
+ })}
+
+ );
+}
+
+function ToolCard({
+ tool,
+ featured = false,
+}: {
+ tool: ToolDefinition;
+ featured?: boolean;
+}) {
+ const tone = toneStyles[tool.tone];
+ const isDarkTone = tool.tone === "slate";
+ const headingColor = isDarkTone ? colors.white : colors.gray[900];
+ const bodyColor = isDarkTone ? "rgba(255,255,255,0.82)" : colors.text.secondary;
+
+ return (
+
+
+
+
+
+
+ {tool.title}
+
+
+
+ {tool.summary}
+
+
+
+
+
+
+ );
+}
+
+function CategoryCards({ tools }: { tools: ToolDefinition[] }) {
+ const grouped = useMemo(() => {
+ return categoryOrder
+ .map((category) => ({
+ category,
+ tools: tools.filter((tool) => tool.category === category),
+ }))
+ .filter((group) => group.tools.length > 0);
+ }, [tools]);
+
+ return (
+
+ {grouped.map((group, index) => (
+
+
+
+ {group.category}
+
+
+ {CATEGORY_DESCRIPTIONS[group.category].label}
+
+
+ {CATEGORY_DESCRIPTIONS[group.category].description}
+
+
+ {group.tools.map((tool) => (
+
+ {tool.title}
+
+ ))}
+
+
+
+ ))}
+
+ );
+}
+
+export default function ToolsShowcase({
+ countryId,
+ tools,
+}: {
+ countryId: string;
+ tools: ToolDefinition[];
+}) {
+ const hasTools = tools.length > 0;
+ const countryLabel = countryLabels[countryId] ?? "your country";
+ const [spotlight, ...rest] = tools;
+
+ return (
+ <>
+
+
+
+
+
+
+
+ Tools for understanding public policy
+
+
+
+
+
+ {hasTools
+ ? `A curated set of calculators, developer tooling, and analysis tools for ${countryLabel}. Built to be opened, tested, and used in real workflows.`
+ : `We do not have a country-specific tools lineup for ${countryLabel} yet. Research remains available now, and more tools will land here as coverage expands.`}
+
+
+
+
+
+
+ {hasTools && spotlight && (
+
+ )}
+
+ {hasTools && rest.length > 0 && (
+
+
+
+ {rest.map((tool, index) => (
+
+
+
+ ))}
+
+
+
+ )}
+
+ {hasTools && (
+
+
+
+
+
+ Tool context
+
+
+ Use this page to launch a tool. Use research to read findings.
+
+
+ Tools should feel like the next action. Research should
+ feel like supporting evidence and interpretation. Keeping that
+ distinction obvious makes the page faster to understand.
+
+
+
+
+
+
+
+
+
+
+ Tools
+
+
+ Built for action.
+
+
+ Launch a calculator, install a tool, compare scenarios,
+ or test inputs directly.
+
+
+
+
+
+ Research
+
+
+ Built for interpretation.
+
+
+ Read findings, review charts, and understand the
+ context behind the numbers.
+
+
+ Go to research
+
+
+
+
+
+
+ {[
+ {
+ title: "Action first",
+ body: "Each tool card leads with the next thing a visitor can do.",
+ },
+ {
+ title: "Distinct types",
+ body: "Calculators, developer tools, and emulators stay visibly separate.",
+ },
+ {
+ title: "Real proof",
+ body: "Previews show interfaces, outputs, and use cases instead of filler copy.",
+ },
+ ].map((item) => (
+
+
+ {item.title}
+
+
+ {item.body}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ )}
+ >
+ );
+}
diff --git a/website/src/data/tools.ts b/website/src/data/tools.ts
new file mode 100644
index 000000000..e99efb918
--- /dev/null
+++ b/website/src/data/tools.ts
@@ -0,0 +1,271 @@
+import type { CountryId } from "@/lib/countries";
+
+export type ToolCategory =
+ | "Policy calculators"
+ | "Developer tools"
+ | "Emulators and analysis tools";
+
+export type ToolTone = "teal" | "slate" | "amber" | "rose" | "sky";
+
+export type ToolPreview =
+ | {
+ type: "terminal";
+ lines: Array<{
+ kind: "comment" | "command" | "prompt" | "output" | "success";
+ text: string;
+ }>;
+ }
+ | {
+ type: "metrics";
+ eyebrow: string;
+ items: Array<{ label: string; value: string }>;
+ }
+ | {
+ type: "image";
+ src: string;
+ alt: string;
+ badge?: string;
+ objectPosition?: string;
+ };
+
+export interface ToolAction {
+ label: string;
+ href: string;
+ external?: boolean;
+}
+
+export interface ToolDefinition {
+ slug: string;
+ title: string;
+ summary: string;
+ category: ToolCategory;
+ countryIds: Array;
+ primaryAction: ToolAction;
+ tone: ToolTone;
+ preview: ToolPreview;
+ priority: number;
+}
+
+export const CATEGORY_DESCRIPTIONS: Record<
+ ToolCategory,
+ { label: string; description: string }
+> = {
+ "Policy calculators": {
+ label: "Estimate household and policy impacts quickly.",
+ description:
+ "Public-facing tools that turn complicated tax and benefit rules into usable calculators.",
+ },
+ "Developer tools": {
+ label: "Build analysis faster.",
+ description:
+ "Tools for analysts and engineers who need direct access to PolicyEngine-powered workflows.",
+ },
+ "Emulators and analysis tools": {
+ label: "Compare methods and stress-test assumptions.",
+ description:
+ "Tools designed for technical analysis, policy comparison, and deeper model exploration.",
+ },
+};
+
+const toolDefinitions: ToolDefinition[] = [
+ {
+ slug: "claude-plugin",
+ title: "Claude plugin",
+ summary:
+ "Run microsimulations, model reforms, and generate analysis directly from your terminal.",
+ category: "Developer tools",
+ countryIds: ["us", "uk"],
+ primaryAction: {
+ label: "Open tool",
+ href: "/{countryId}/claude-plugin",
+ },
+ tone: "slate",
+ preview: {
+ type: "terminal",
+ lines: [
+ { kind: "comment", text: "# Ask a policy question" },
+ {
+ kind: "prompt",
+ text: "Estimate the impact of expanding the Child Tax Credit",
+ },
+ { kind: "output", text: "Running microsimulation on household microdata..." },
+ { kind: "success", text: "Impact summary ready with distributional results" },
+ ],
+ },
+ priority: 10,
+ },
+ {
+ slug: "taxsim",
+ title: "Taxsim emulator",
+ summary:
+ "Explore TAXSIM-style tax calculations in a more modern PolicyEngine interface.",
+ category: "Emulators and analysis tools",
+ countryIds: ["us"],
+ primaryAction: {
+ label: "Open tool",
+ href: "/us/taxsim",
+ },
+ tone: "sky",
+ preview: {
+ type: "metrics",
+ eyebrow: "Compare inputs and outputs",
+ items: [
+ { label: "Federal tax", value: "$11,420" },
+ { label: "Payroll tax", value: "$6,885" },
+ { label: "Effective rate", value: "18.6%" },
+ ],
+ },
+ priority: 9,
+ },
+ {
+ slug: "keep-your-pay-act",
+ title: "Keep Your Pay Act calculator",
+ summary:
+ "Estimate how Senator Booker's proposal changes taxes, credits, and net income.",
+ category: "Policy calculators",
+ countryIds: ["us"],
+ primaryAction: {
+ label: "Open tool",
+ href: "/us/keep-your-pay-act",
+ },
+ tone: "amber",
+ preview: {
+ type: "image",
+ src: "/assets/posts/keep-your-pay-act-calculator.png",
+ alt: "Keep Your Pay Act calculator interface",
+ objectPosition: "center top",
+ },
+ priority: 8,
+ },
+ {
+ slug: "tanf-calculator",
+ title: "TANF calculator",
+ summary:
+ "Check TANF eligibility and benefit amounts across all 50 states and DC.",
+ category: "Policy calculators",
+ countryIds: ["us"],
+ primaryAction: {
+ label: "Open tool",
+ href: "https://policyengine.github.io/tanf-calculator/",
+ external: true,
+ },
+ tone: "teal",
+ preview: {
+ type: "image",
+ src: "/assets/posts/tanf-calculator.png",
+ alt: "TANF calculator interface",
+ objectPosition: "center top",
+ },
+ priority: 7,
+ },
+ {
+ slug: "marriage-calculator",
+ title: "Marriage incentive calculator",
+ summary:
+ "See how marriage changes taxes, benefits, and take-home income for a household.",
+ category: "Policy calculators",
+ countryIds: ["us", "uk"],
+ primaryAction: {
+ label: "Open tool",
+ href: "https://marriage-zeta-beryl.vercel.app/",
+ external: true,
+ },
+ tone: "rose",
+ preview: {
+ type: "image",
+ src: "/assets/posts/marriage-calculator.webp",
+ alt: "Marriage incentive calculator charts and controls",
+ objectPosition: "center top",
+ },
+ priority: 6,
+ },
+ {
+ slug: "uk-student-loan-calculator",
+ title: "Student loan deductions calculator",
+ summary:
+ "Analyse repayments, marginal tax rates, and take-home pay for UK graduates.",
+ category: "Policy calculators",
+ countryIds: ["uk"],
+ primaryAction: {
+ label: "Open tool",
+ href: "https://uk-student-loan-calculator.vercel.app/",
+ external: true,
+ },
+ tone: "sky",
+ preview: {
+ type: "image",
+ src: "/assets/posts/uk-student-loan-calculator.webp",
+ alt: "Student loan deductions calculator charts",
+ objectPosition: "center top",
+ },
+ priority: 8,
+ },
+ {
+ slug: "uk-salary-sacrifice-tool",
+ title: "Salary sacrifice cap analysis tool",
+ summary:
+ "Test how caps on NI-exempt pension salary sacrifice affect revenue and households.",
+ category: "Emulators and analysis tools",
+ countryIds: ["uk"],
+ primaryAction: {
+ label: "Open tool",
+ href: "https://policyengine.github.io/uk-salary-sacrifice-analysis/",
+ external: true,
+ },
+ tone: "teal",
+ preview: {
+ type: "metrics",
+ eyebrow: "Test policy options",
+ items: [
+ { label: "Revenue", value: "GBP1.4B" },
+ { label: "Affected workers", value: "2.1M" },
+ { label: "Median change", value: "-GBP86" },
+ ],
+ },
+ priority: 7,
+ },
+ {
+ slug: "local-areas-dashboard",
+ title: "UK local areas dashboard",
+ summary:
+ "Explore how national policies affect constituencies and local authorities across the UK.",
+ category: "Emulators and analysis tools",
+ countryIds: ["uk"],
+ primaryAction: {
+ label: "Open tool",
+ href: "https://local-area.vercel.app/",
+ external: true,
+ },
+ tone: "amber",
+ preview: {
+ type: "metrics",
+ eyebrow: "Zoom in geographically",
+ items: [
+ { label: "Coverage", value: "650 seats" },
+ { label: "Local areas", value: "370+" },
+ { label: "Views", value: "Map + tables" },
+ ],
+ },
+ priority: 6,
+ },
+];
+
+function resolveActionHref(href: string, countryId: string) {
+ return href.replaceAll("{countryId}", countryId);
+}
+
+export function getToolsForCountry(countryId: string): ToolDefinition[] {
+ return toolDefinitions
+ .filter((tool) =>
+ tool.countryIds.includes("all") ||
+ tool.countryIds.includes(countryId as CountryId),
+ )
+ .map((tool) => ({
+ ...tool,
+ primaryAction: {
+ ...tool.primaryAction,
+ href: resolveActionHref(tool.primaryAction.href, countryId),
+ },
+ }))
+ .sort((left, right) => right.priority - left.priority);
+}