Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,13 @@ export default function App() {
activeNav={activeNav}
/>

<main className="max-w-7xl mx-auto px-4 sm:px-6">
<section id="models" className="pt-12 pb-16 sm:pt-16 sm:pb-20">
<main id="main" className="max-w-7xl mx-auto px-4 sm:px-6">
<h1 className="sr-only">PolicyBench leaderboard</h1>
<section
id="models"
aria-labelledby="leaderboard-heading"
className="pt-12 pb-16 sm:pt-16 sm:pb-20"
>
<ModelLeaderboard
data={data}
selectedView={selectedView}
Expand Down
68 changes: 68 additions & 0 deletions app/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,51 @@ a {
color: inherit;
}

/* Visible focus ring for every interactive element that doesn't define its own.
Uses :focus-visible so mouse clicks don't show the ring; keyboard / screen
reader navigation does. */
:where(a, button, [role="button"], input, select, textarea, summary, [tabindex]):focus-visible {
outline: 2px solid var(--color-primary-strong);
outline-offset: 2px;
border-radius: 4px;
}

/* Skip-to-content link (visually hidden until focused). */
.skip-to-content {
position: absolute;
top: 0;
left: 0;
padding: 0.5rem 0.75rem;
background: var(--color-bg);
color: var(--color-primary-strong);
border-radius: 0 0 8px 0;
border: 2px solid var(--color-primary-strong);
font-weight: 600;
z-index: 100;
transform: translateY(-200%);
transition: transform 120ms var(--ease-out);
}

.skip-to-content:focus,
.skip-to-content:focus-visible {
transform: translateY(0);
outline: none;
}

/* Tailwind already ships .sr-only, but redefine here for safety in case the
Tailwind generator skips it. */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}

::-webkit-scrollbar {
width: 5px;
}
Expand Down Expand Up @@ -99,6 +144,29 @@ a {
animation: glow-pulse 4s ease-in-out infinite;
}

/* Respect prefers-reduced-motion: reduce. Snap to end states, no scroll
smoothing, no decorative pulses. The Hero's scroll-driven progress is
gated separately in JS so the header skips RAF work. */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
scroll-behavior: auto !important;
}

html {
scroll-behavior: auto;
}

.animate-fade-up,
.glow-primary {
animation: none !important;
}
}

.eyebrow {
font-family: var(--f-mono);
font-size: 11px;
Expand Down
12 changes: 10 additions & 2 deletions app/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import type { Metadata } from "next";
import "./globals.css";

export const metadata: Metadata = {
title: "PolicyBench, by PolicyEngine",
title: {
default: "PolicyBench, by PolicyEngine",
template: "%s — PolicyBench",
},
description:
"Benchmarking no-tools policy calculation across frontier models.",
};
Expand All @@ -14,7 +17,12 @@ export default function RootLayout({
}>) {
return (
<html lang="en">
<body className="antialiased">{children}</body>
<body className="antialiased">
<a href="#main" className="skip-to-content">
Skip to main content
</a>
{children}
</body>
</html>
);
}
42 changes: 38 additions & 4 deletions app/src/app/paper/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
/* eslint-disable @next/next/no-img-element */
import type { Metadata } from "next";
import Link from "next/link";

import SiteHeader from "../../components/SiteHeader";

const SNAPSHOT_DATE_LABEL = "Snapshot 2026-05-01";

export const metadata: Metadata = {
title: "Paper",
description:
"PolicyBench paper — frozen 2026-05-01 manuscript snapshot scored against PolicyEngine reference outputs.",
};

const manuscriptPaths = {
pdf: "/paper/policybench.pdf",
web: "/paper/web/index.html?v=20260501",
Expand All @@ -30,7 +37,8 @@ export default function PaperPage() {
);

return (
<main className="min-h-screen bg-void">
<main id="main" className="min-h-screen bg-void">
<h1 className="sr-only">PolicyBench paper</h1>
<SiteHeader
actionLink={{
label: "Benchmark",
Expand Down Expand Up @@ -83,15 +91,41 @@ export default function PaperPage() {
</Link>
</div>

<section className="mt-8 overflow-hidden rounded-3xl border border-border bg-card">
<div className="border-b border-border px-5 py-3 text-sm text-text-secondary">
<section
aria-labelledby="manuscript-section-heading"
className="mt-8 overflow-hidden rounded-3xl border border-border bg-card"
>
<div
id="manuscript-section-heading"
className="border-b border-border px-5 py-3 text-sm text-text-secondary"
>
Frozen manuscript snapshot
</div>
<iframe
src={manuscriptPaths.web}
title="PolicyBench paper"
title="PolicyBench manuscript (embedded)"
loading="lazy"
sandbox="allow-same-origin allow-popups allow-popups-to-escape-sandbox"
referrerPolicy="same-origin"
className="block h-[calc(100vh-16rem)] min-h-[720px] w-full border-0 bg-white"
/>
<div className="border-t border-border px-5 py-3 text-sm">
<a
href="#paper-top"
className="text-text-secondary hover:text-primary-strong"
>
↑ Back to top
</a>
<span className="mx-2 text-text-muted" aria-hidden>
·
</span>
<a
href={manuscriptPaths.web}
className="text-text-secondary hover:text-primary-strong"
>
Open manuscript in a new page
</a>
</div>
</section>
</div>
</main>
Expand Down
12 changes: 11 additions & 1 deletion app/src/app/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@

--color-text: var(--pe-color-text-primary);
--color-text-secondary: var(--pe-color-text-secondary);
--color-text-muted: var(--pe-color-text-tertiary);
/* Was --pe-color-text-tertiary (#9CA3AF) which fails 4.5:1 on white at small
sizes. Switch to gray-600 (#4B5563), which clears 4.5:1 (8.59:1 actual). */
--color-text-muted: var(--pe-color-gray-600);
/* Reserve --color-text-muted-light for ≥18px decorative use only. */
--color-text-muted-light: var(--pe-color-text-tertiary);

--color-primary: var(--pe-color-primary-500);
--color-primary-strong: var(--pe-color-primary-700);
Expand All @@ -18,10 +22,16 @@
--color-info-soft: color-mix(in srgb, var(--pe-color-info) 12%, var(--pe-color-bg-primary));
--color-warning: var(--pe-color-warning);
--color-warning-soft: color-mix(in srgb, var(--pe-color-warning) 14%, var(--pe-color-bg-primary));
/* Accessible warning text for use on white or warning-soft. */
--color-warning-text: var(--pe-color-text-warning);
--color-danger: var(--pe-color-error);
--color-danger-soft: color-mix(in srgb, var(--pe-color-error) 12%, var(--pe-color-bg-primary));
/* Accessible danger text (gray-700-tinted red). */
--color-danger-text: #b91c1c;
--color-success: var(--pe-color-primary-600);
--color-success-soft: color-mix(in srgb, var(--pe-color-primary-200) 68%, var(--pe-color-bg-primary));
/* Accessible success text matching success-soft fill at AA. */
--color-success-text: var(--pe-color-primary-700);
--color-neutral: var(--pe-color-gray-600);
--color-neutral-soft: color-mix(in srgb, var(--pe-color-border-light) 72%, var(--pe-color-bg-primary));

Expand Down
23 changes: 20 additions & 3 deletions app/src/components/ExplanationTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default function ExplanationTooltip({
children?: ReactNode;
}) {
const triggerRef = useRef<HTMLButtonElement | null>(null);
const tooltipRef = useRef<HTMLDivElement | null>(null);
const [open, setOpen] = useState(false);
const [position, setPosition] = useState<TooltipPosition | null>(null);
const tooltipId = useId();
Expand All @@ -40,13 +41,22 @@ export default function ExplanationTooltip({
});
};

const onKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
setOpen(false);
triggerRef.current?.focus();
}
};

updatePosition();
window.addEventListener("scroll", updatePosition, true);
window.addEventListener("resize", updatePosition);
document.addEventListener("keydown", onKeyDown);

return () => {
window.removeEventListener("scroll", updatePosition, true);
window.removeEventListener("resize", updatePosition);
document.removeEventListener("keydown", onKeyDown);
};
}, [open]);

Expand All @@ -56,23 +66,30 @@ export default function ExplanationTooltip({
ref={triggerRef}
type="button"
aria-describedby={open ? tooltipId : undefined}
aria-label={explanation}
aria-expanded={open}
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
onFocus={() => setOpen(true)}
onBlur={() => setOpen(false)}
className="inline-flex rounded-full border border-border px-1.5 py-0.5 text-[10px] leading-none text-text-muted transition-colors hover:border-primary/40 hover:text-text focus:border-primary/50 focus:text-text focus:outline-none cursor-help"
className="inline-flex rounded-full border border-border px-1.5 py-0.5 text-[10px] leading-none text-text-muted transition-colors hover:border-primary-strong/40 hover:text-text focus:border-primary-strong/60 focus:text-text cursor-help"
>
{children}
<span className="sr-only">{`: ${explanation}`}</span>
</button>
{open &&
position &&
typeof document !== "undefined" &&
createPortal(
<div
id={tooltipId}
ref={tooltipRef}
role="tooltip"
className="pointer-events-none fixed z-[100] max-w-xs -translate-x-1/2 rounded-xl border border-border bg-surface px-3 py-2 text-left text-xs leading-relaxed text-text shadow-[0_12px_48px_rgba(0,0,0,0.35)]"
// Dropping pointer-events-none lets a mouse user move into the
// tooltip to read long explanations without it dismissing.
// We also keep the tooltip open while the user hovers it.
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
className="fixed z-[100] max-w-xs -translate-x-1/2 rounded-xl border border-border bg-surface px-3 py-2 text-left text-xs leading-relaxed text-text shadow-[0_12px_48px_rgba(0,0,0,0.35)]"
style={{
top: position.top,
left: position.left,
Expand Down
Loading