Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
6282031
Initialized FutureEval branding
aseckin Dec 27, 2025
bab7e42
Redesigned Model Leaderboard
aseckin Dec 27, 2025
6d0f5f0
fixed horizontal leaderboard hijacking the vertical scroll
aseckin Dec 31, 2025
6ba6022
performance chart updates
aseckin Dec 31, 2025
a856bd6
Merge branch 'main' into futureeval-branding
aseckin Jan 5, 2026
0bcd05b
Applied FutureEval branding to all pages
aseckin Jan 5, 2026
fb09609
Merge branch 'main' into futureeval-branding
aseckin Jan 6, 2026
15d4148
Merge branch 'main' into futureeval-branding
aseckin Jan 7, 2026
6038e6b
Merge branch 'main' into futureeval-branding
aseckin Jan 8, 2026
c8b56a7
Improvements to Performance Over Time chart
aseckin Jan 8, 2026
34bdb4f
Merge branch 'main' into futureeval-branding
aseckin Jan 9, 2026
8105161
Further theming and styling
aseckin Jan 9, 2026
511eb2e
Merge branch 'main' into futureeval-branding
aseckin Jan 15, 2026
2fcdf7a
Applied new FutureEval branding
aseckin Jan 15, 2026
6549a2f
Fix TypeScript type narrowing for profileHref
aseckin Jan 15, 2026
6712df3
Implement PR feedback
aseckin Jan 15, 2026
adb1fe0
Fix TypeScript errors with LeaderboardDetails type in error handling
aseckin Jan 15, 2026
c92ef75
Another fix for Typescript errors with LeaderboardDetails
aseckin Jan 15, 2026
de9d4f0
Implement new hero section
aseckin Jan 15, 2026
7cd2aa9
Implement PR feedback, fix typo
aseckin Jan 15, 2026
aa9af9e
Merge branch 'main' into futureeval-branding
aseckin Jan 15, 2026
1c5ce4a
Merge branch 'main' into futureeval-branding
aseckin Jan 15, 2026
38fe35e
Renewed News section, final styling polish
aseckin Jan 16, 2026
e3b1629
Implement PR feedback
aseckin Jan 16, 2026
565eac0
Merge branch 'main' into futureeval-branding
aseckin Jan 16, 2026
31224f4
Merge branch 'main' into futureeval-branding
aseckin Jan 19, 2026
89e8580
Address PR feedback
aseckin Jan 19, 2026
827e8d6
Applied formatting
aseckin Jan 19, 2026
f2bb920
chore: sync en.json with main branch
aseckin Jan 20, 2026
09737f2
refactor(futureeval): remove translations, use hardcoded English strings
aseckin Jan 20, 2026
86d0f7b
refactor(futureeval): orbit cleanup - split files, use CSS vars for s…
aseckin Jan 20, 2026
952d1af
fix(aib): right-align reference line labels in performance chart
aseckin Jan 20, 2026
1ab3806
fix(futureeval): escape apostrophes in JSX
aseckin Jan 20, 2026
85c40eb
applied Prettier formatting
aseckin Jan 20, 2026
688c3f8
Merge branch 'main' into futureeval-branding
aseckin Jan 20, 2026
97b480d
fix(futureeval): update orbit index exports after refactoring
aseckin Jan 20, 2026
09fc5c6
Merge branch 'futureeval-branding' of https://github.com/Metaculus/re…
aseckin Jan 20, 2026
68bc883
Improve accessibility and fix minor issues in FutureEval components
aseckin Jan 20, 2026
9a12df4
Merge branch 'main' into futureeval-branding
aseckin Jan 20, 2026
df2f3a9
Merge branch 'main' into futureeval-branding
aseckin Jan 21, 2026
ac12607
restore package-lock.json from main
aseckin Jan 21, 2026
b6f672f
Merge branch 'main' into futureeval-branding
aseckin Jan 21, 2026
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
27 changes: 27 additions & 0 deletions front_end/src/app/(futureeval)/futureeval/assets/FE-logo-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions front_end/src/app/(futureeval)/futureeval/assets/FE-logo-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"use client";

import Link from "next/link";
import { PropsWithChildren } from "react";

import cn from "@/utils/core/cn";

import { FE_COLORS, FE_TYPOGRAPHY } from "../../theme";

type Props = PropsWithChildren<{
title?: React.ReactNode;
subtitle?: React.ReactNode;
}>;

/**
* FutureEval-specific subsection header with left alignment
*/
const FutureEvalSubsectionHeader: React.FC<Props> = ({
title,
subtitle,
children,
}) => {
return (
<>
{title != null && (
<h3
className={cn(
"m-0 text-left",
FE_TYPOGRAPHY.h2,
FE_COLORS.textHeading
)}
>
{title}
</h3>
)}

{subtitle != null && (
<p
className={cn(
"m-0 mt-3 text-left",
FE_TYPOGRAPHY.body,
FE_COLORS.textSubheading
)}
>
{subtitle}
</p>
)}
{children}
</>
);
};

/**
* Forecasting Performance Over Time header (left-aligned)
*/
export const FutureEvalForecastingPerformanceHeader: React.FC = () => {
return (
<FutureEvalSubsectionHeader
title="Forecasting Performance Over Time"
subtitle="Model scores from the model leaderboard by release date."
/>
);
};

/**
* Pros vs Bots header with left alignment
*/
export const FutureEvalProsVsBotsSectionHeader: React.FC = () => {
return (
<FutureEvalSubsectionHeader
title="How much Pros beat Bots"
subtitle={
<>
Metaculus Pro Forecasters have beaten Bots every quarter of our{" "}
<Link
className={cn(FE_TYPOGRAPHY.link, FE_COLORS.textAccent)}
href="/notebooks/38928/futureeval-resources-page/#what-is-the-pro-vs-bots-graph"
>
AI Benchmarking Tournaments
</Link>{" "}
so far.
</>
}
/>
);
};

export default FutureEvalSubsectionHeader;
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import AIBBenchmarkForecastingPerformance from "@/app/(main)/aib/components/aib/tabs/benchmark/performance-over-time/aib-benchmark-forecasting-performance";
import { AIBProsVsBotsDiffExample } from "@/app/(main)/aib/components/aib/tabs/benchmark/pros-vs-bots/aib-pros-vs-bots-comparison";

import {
FutureEvalForecastingPerformanceHeader,
FutureEvalProsVsBotsSectionHeader,
} from "./futureeval-benchmark-headers";
import FutureEvalModelBenchmark from "./futureeval-model-benchmark";

const FutureEvalBenchmarkTab: React.FC = () => {
return (
<>
<div>
<FutureEvalModelBenchmark />
</div>

{/* Forecasting Performance Over Time */}
<div>
<FutureEvalForecastingPerformanceHeader />
<AIBBenchmarkForecastingPerformance />
</div>

{/* Pros vs Bots */}
<div>
<FutureEvalProsVsBotsSectionHeader />
<AIBProsVsBotsDiffExample />
</div>
</>
);
};

export default FutureEvalBenchmarkTab;
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
"use client";

import { FloatingPortal } from "@floating-ui/react";
import { faUsers } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { StaticImageData } from "next/image";
import { useRouter } from "next/navigation";
import { useState } from "react";

import { LightDarkIcon } from "@/app/(main)/aib/components/aib/light-dark-icon";
import cn from "@/utils/core/cn";

import { FE_COLORS } from "../../theme";

type Props = {
heightPct: number;
model: {
id: string;
name: string;
score: number;
contributionCount: number;
iconLight?: StaticImageData | string;
iconDark?: StaticImageData | string;
isAggregate?: boolean;
};
};

const FutureEvalModelBar: React.FC<Props> = ({ heightPct, model }) => {
const router = useRouter();
const score = Math.round(model.score * 100) / 100;
const [isHovered, setIsHovered] = useState(false);
const [mousePos, setMousePos] = useState({ x: 0, y: 0 });

const handleClick = () => {
router.push(
`/futureeval/leaderboard?highlight=${encodeURIComponent(model.id)}`
);
};

const handleMouseMove = (e: React.MouseEvent) => {
setMousePos({ x: e.clientX, y: e.clientY });
};

const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter" || e.key === " " || e.key === "Spacebar") {
e.preventDefault();
handleClick();
}
};

return (
<>
<div
className="flex h-full cursor-pointer flex-col items-center pb-28 pt-5 focus:outline-none focus-visible:ring-2 focus-visible:ring-futureeval-primary-light dark:focus-visible:ring-futureeval-primary-dark sm:pt-6"
onClick={handleClick}
onKeyDown={handleKeyDown}
tabIndex={0}
role="button"
aria-label={`View ${model.name} details, score: ${score}`}
>
{/* Bar area - flex-1 takes remaining height, aligns bar at bottom */}
<div className="relative flex w-full flex-1 flex-col items-center justify-end">
{/* Score label - sits above the bar */}
<span className="mb-1 shrink-0 text-[10px] font-medium tabular-nums text-gray-800 dark:text-gray-800-dark sm:text-xs">
{score}
</span>

{/* The actual bar with 1px border - hover states and tooltip trigger */}
<div
className={cn(
"relative flex w-full flex-col items-center rounded-t-md border pt-2 transition-all duration-200",
model.isAggregate
? cn(
FE_COLORS.barAggregateBorder,
FE_COLORS.barAggregateBg,
FE_COLORS.barAggregateHover
)
: "border-gray-800 bg-gray-0 hover:bg-gray-300 dark:border-gray-800-dark dark:bg-gray-0-dark dark:hover:bg-gray-300-dark"
)}
style={{ height: `${heightPct}%`, minHeight: "48px" }}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
onMouseMove={handleMouseMove}
>
{/* Model icon at the TOP of the bar */}
{model.isAggregate ? (
<FontAwesomeIcon
icon={faUsers}
className={cn(
"h-5 w-5 shrink-0 sm:h-5 sm:w-5",
FE_COLORS.barAggregateIcon
)}
/>
) : (
(model.iconLight || model.iconDark) && (
<LightDarkIcon
alt={model.name}
light={model.iconLight}
dark={model.iconDark}
sizePx={20}
className="shrink-0 sm:!h-6 sm:!w-6"
/>
)
)}
</div>

{/* Small baseline at the bottom */}
<div className="absolute bottom-0 left-0 right-0 h-px bg-gray-800 dark:bg-gray-800-dark" />
</div>

{/* Model name below bar - rotated 45 degrees with connecting line */}
<div className="relative h-0 w-full">
{/* Connecting line - centered */}
<div className="absolute left-1/2 h-2 w-px -translate-x-1/2 bg-gray-800 dark:bg-gray-800-dark" />
{/* Rotated label - starts at end of line */}
<span
className="absolute left-1/2 top-2 ml-[3px] line-clamp-1 w-[140px] origin-top-left rotate-45 text-xs font-medium text-gray-800 dark:text-gray-800-dark sm:top-3 sm:line-clamp-2 sm:w-[140px] sm:text-sm sm:leading-4"
title={model.name}
>
{model.name}
</span>
</div>
</div>

{/* Cursor-following tooltip */}
{isHovered && (
<FloatingPortal>
<div
className="pointer-events-none z-100 rounded border border-gray-800 bg-gray-0 p-3 dark:border-gray-800-dark dark:bg-gray-0-dark"
style={{
position: "fixed",
left: mousePos.x + 12,
top: mousePos.y + 12,
}}
>
<div className="flex flex-col gap-2 text-sm">
<div className="flex justify-between gap-4">
<span className="text-[10px] text-gray-800 dark:text-gray-800-dark sm:text-xs">
Score:
</span>
<span className="text-[10px] font-medium tabular-nums text-gray-800 dark:text-gray-800-dark sm:text-xs">
{score}
</span>
</div>
<div className="flex justify-between gap-4">
<span className="text-[10px] text-gray-800 dark:text-gray-800-dark sm:text-xs">
Forecasts:
</span>
<span className="text-[10px] font-medium tabular-nums text-gray-800 dark:text-gray-800-dark sm:text-xs">
{model.contributionCount}
</span>
</div>
</div>
</div>
</FloatingPortal>
)}
</>
);
};

export default FutureEvalModelBar;
Loading
Loading