Skip to content

Commit 3f7cc29

Browse files
committed
Redesign homepage around narrative flow and curated audience paths
1 parent 49d2697 commit 3f7cc29

10 files changed

Lines changed: 782 additions & 139 deletions

File tree

src/App.jsx

Lines changed: 85 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,110 @@
1-
import { useState, useEffect } from 'react';
1+
import { useState } from 'react';
22
import Nav from './components/Nav.jsx';
33
import Hero from './components/hero/Hero.jsx';
4-
import TrackTabs from './components/TrackTabs.jsx';
4+
import WhyRecourse from './components/WhyRecourse.jsx';
5+
import FeaturedResources from './components/FeaturedResources.jsx';
6+
import PathCards from './components/PathCards.jsx';
57
import ResearchTrack from './components/tracks/ResearchTrack/index.jsx';
68
import BuildTrack from './components/tracks/BuildTrack/index.jsx';
79
import DeployTrack from './components/tracks/DeployTrack/index.jsx';
810
import Footer from './components/Footer.jsx';
911

1012
export default function App() {
1113
const [activeTab, setActiveTab] = useState('research');
12-
const [isMobile, setIsMobile] = useState(false);
1314

14-
useEffect(() => {
15-
const check = () => setIsMobile(window.innerWidth < 1024);
16-
check();
17-
window.addEventListener('resize', check);
18-
return () => window.removeEventListener('resize', check);
19-
}, []);
20-
21-
const TRACK_HEADERS = [
22-
{ key: 'research', label: 'Explore Research', icon: '🔬', color: '#2563eb' },
23-
{ key: 'build', label: 'Build with Recourse', icon: '⚙', color: '#7c3aed' },
24-
{ key: 'deploy', label: 'Deploy', icon: '🏢', color: '#059669' },
15+
const TRACKS = [
16+
{
17+
key: 'research',
18+
label: 'Research',
19+
kicker: 'Map the field',
20+
title: 'Survey the literature and identify open questions.',
21+
description: 'For scholars and advanced readers who need foundations, distinctions, and a clear view of the active research landscape.',
22+
bullets: ['Search seminal papers and newer threads', 'Compare causal, optimization, and game-theoretic approaches', 'Capture open problems worth pursuing'],
23+
cta: 'Explore research',
24+
color: '#2453a6',
25+
softColor: '#dbe7ff',
26+
panelId: 'research-panel',
27+
component: <ResearchTrack />,
28+
},
29+
{
30+
key: 'build',
31+
label: 'Build',
32+
kicker: 'Prototype confidently',
33+
title: 'Move from theory into implementation patterns.',
34+
description: 'For builders evaluating methods, metrics, and packages before integrating recourse into real model pipelines.',
35+
bullets: ['Compare methods by assumptions and tradeoffs', 'Inspect evaluation metrics before choosing a benchmark', 'Find practical package starting points'],
36+
cta: 'See implementation guidance',
37+
color: '#8a4b18',
38+
softColor: '#f7e5d1',
39+
panelId: 'build-panel',
40+
component: <BuildTrack />,
41+
},
42+
{
43+
key: 'deploy',
44+
label: 'Deploy',
45+
kicker: 'Operate responsibly',
46+
title: 'Translate recourse into product, policy, and governance.',
47+
description: 'For leaders and deployment teams aligning user experience, compliance, and institutional value.',
48+
bullets: ['Connect technical metrics to business outcomes', 'Understand governance and audit implications', 'Frame recourse as an organizational capability'],
49+
cta: 'Review deployment guidance',
50+
color: '#1d7a55',
51+
softColor: '#dff3ea',
52+
panelId: 'deploy-panel',
53+
component: <DeployTrack />,
54+
},
2555
];
2656

57+
const activeTrack = TRACKS.find((track) => track.key === activeTab) ?? TRACKS[0];
58+
2759
return (
2860
<>
2961
<Nav />
3062
<Hero />
63+
<main className="page-shell">
64+
<WhyRecourse />
65+
<FeaturedResources />
66+
<PathCards activePath={activeTab} setActivePath={setActiveTab} tracks={TRACKS} />
3167

32-
<section style={{ borderTop: '1px solid #e2e8f0' }}>
33-
{/* Section intro */}
34-
<div style={{ textAlign: 'center', padding: '3rem 2rem 0' }}>
35-
<div style={{ fontSize: '.72rem', fontWeight: 700, color: '#2563eb', textTransform: 'uppercase', letterSpacing: '.12em', marginBottom: '.6rem' }}>
36-
Choose Your Path
68+
<section id={activeTrack.panelId} className="section-shell active-track-shell">
69+
<div className="active-track-header">
70+
<div>
71+
<span className="eyebrow">Active Path</span>
72+
<h2>{activeTrack.label}</h2>
73+
</div>
74+
<div className="active-track-switcher">
75+
{TRACKS.map((track) => (
76+
<button
77+
key={track.key}
78+
type="button"
79+
className={track.key === activeTab ? 'active' : ''}
80+
onClick={() => setActiveTab(track.key)}
81+
>
82+
{track.label}
83+
</button>
84+
))}
85+
</div>
3786
</div>
38-
<h2 style={{ fontSize: 'clamp(1.6rem,3vw,2.2rem)', fontWeight: 900, letterSpacing: '-.03em', marginBottom: '.6rem' }}>
39-
Three Tracks, One Mission
40-
</h2>
41-
<p style={{ fontSize: '1rem', color: '#64748b', maxWidth: 560, margin: '0 auto', lineHeight: 1.7 }}>
42-
Whether you study recourse, build it, or deploy it — find exactly what you need.
43-
</p>
44-
</div>
4587

46-
{/* Mobile: tab switcher */}
47-
{isMobile && <TrackTabs active={activeTab} setActive={setActiveTab} />}
48-
49-
{isMobile ? (
50-
<div style={{ maxWidth: 640, margin: '0 auto' }}>
51-
{activeTab === 'research' && <ResearchTrack />}
52-
{activeTab === 'build' && <BuildTrack />}
53-
{activeTab === 'deploy' && <DeployTrack />}
54-
</div>
55-
) : (
56-
/* Desktop: 3 independently-scrollable columns */
57-
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr' }}>
58-
{/* Column label row */}
59-
<div style={{ gridColumn: '1 / -1', display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', borderBottom: '1px solid #e2e8f0' }}>
60-
{TRACK_HEADERS.map((col, i) => (
61-
<a key={col.key} href={`#${col.key}`}
62-
style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8, padding: '1rem',
63-
borderRight: i < 2 ? '1px solid #e2e8f0' : 'none', background: '#f8fafc', textDecoration: 'none',
64-
transition: 'background .2s' }}
65-
onMouseEnter={e => e.currentTarget.style.background = '#fff'}
66-
onMouseLeave={e => e.currentTarget.style.background = '#f8fafc'}>
67-
<span style={{ fontSize: '1.1rem' }}>{col.icon}</span>
68-
<span style={{ fontWeight: 800, fontSize: '.88rem', color: col.color }}>{col.label}</span>
69-
</a>
70-
))}
88+
<div className="active-track-hero card-lift">
89+
<div>
90+
<span className="path-badge" style={{ background: activeTrack.softColor, color: activeTrack.color }}>
91+
{activeTrack.kicker}
92+
</span>
93+
<h3>{activeTrack.title}</h3>
94+
<p>{activeTrack.description}</p>
7195
</div>
96+
<ul>
97+
{activeTrack.bullets.map((bullet) => (
98+
<li key={bullet}>{bullet}</li>
99+
))}
100+
</ul>
101+
</div>
72102

73-
{/* Track columns */}
74-
<div id="research" className="track-col" style={{ borderRight: '1px solid #e2e8f0' }}><ResearchTrack /></div>
75-
<div id="build" className="track-col" style={{ borderRight: '1px solid #e2e8f0' }}><BuildTrack /></div>
76-
<div id="deploy" className="track-col"><DeployTrack /></div>
103+
<div className="track-panel">
104+
{activeTrack.component}
77105
</div>
78-
)}
79-
</section>
106+
</section>
107+
</main>
80108

81109
<Footer />
82110
</>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { PAPERS } from '../data/papers.js';
2+
import { METHODS } from '../data/methods.js';
3+
import { BUSINESS_MAP } from '../data/business.js';
4+
5+
const resourceCards = [
6+
{
7+
eyebrow: 'Foundational paper',
8+
title: PAPERS[0].title,
9+
meta: `${PAPERS[0].authors} · ${PAPERS[0].year}`,
10+
body: 'Start with the paper that established the modern language around actionable counterfactual guidance.',
11+
href: '#research-panel',
12+
},
13+
{
14+
eyebrow: 'Builder reference',
15+
title: `${METHODS[0].name} and peer methods`,
16+
meta: `${METHODS.length}+ implementation patterns`,
17+
body: 'Compare practical approaches by runtime, validity, sparsity, and data assumptions before you prototype.',
18+
href: '#build-panel',
19+
},
20+
{
21+
eyebrow: 'Deployment lens',
22+
title: BUSINESS_MAP[2].business,
23+
meta: `${BUSINESS_MAP[2].metric} as an operating concern`,
24+
body: 'Frame recourse as product design, policy, and governance work rather than a purely technical feature.',
25+
href: '#deploy-panel',
26+
},
27+
];
28+
29+
export default function FeaturedResources() {
30+
return (
31+
<section className="section-shell">
32+
<div className="section-heading">
33+
<span className="eyebrow">Featured Resources</span>
34+
<h2>Start with a few well-chosen entry points.</h2>
35+
<p>
36+
The field is wide. These anchors give first-time visitors a cleaner way
37+
into the research, implementation, and deployment questions that matter most.
38+
</p>
39+
</div>
40+
41+
<div className="resource-grid">
42+
{resourceCards.map((card) => (
43+
<a key={card.title} className="resource-card card-lift" href={card.href}>
44+
<span className="resource-eyebrow">{card.eyebrow}</span>
45+
<h3>{card.title}</h3>
46+
<div className="resource-meta">{card.meta}</div>
47+
<p>{card.body}</p>
48+
<span className="resource-link">Open section</span>
49+
</a>
50+
))}
51+
</div>
52+
</section>
53+
);
54+
}

src/components/Nav.jsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ export default function Nav() {
99
return () => window.removeEventListener('scroll', fn);
1010
}, []);
1111

12-
const links = ['Research', 'Build', 'Deploy', 'About'];
12+
const links = [
13+
{ label: 'About', href: '#about' },
14+
{ label: 'Paths', href: '#paths' },
15+
{ label: 'Research', href: '#research-panel' },
16+
{ label: 'Deploy', href: '#deploy-panel' },
17+
];
1318

1419
return (
1520
<nav style={{
@@ -35,12 +40,12 @@ export default function Nav() {
3540

3641
{/* Links */}
3742
<div style={{ display: 'flex', alignItems: 'center', gap: '1.75rem' }}>
38-
{links.map(l => (
39-
<a key={l} href={`#${l.toLowerCase()}`}
43+
{links.map((link) => (
44+
<a key={link.label} href={link.href}
4045
style={{ fontSize: '.85rem', fontWeight: 600, color: '#475569', textDecoration: 'none', transition: 'color .15s' }}
4146
onMouseEnter={e => e.currentTarget.style.color = '#0f172a'}
4247
onMouseLeave={e => e.currentTarget.style.color = '#475569'}>
43-
{l}
48+
{link.label}
4449
</a>
4550
))}
4651
<a href="https://github.com/charmlab" target="_blank" rel="noopener noreferrer"

src/components/PathCards.jsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
export default function PathCards({ activePath, setActivePath, tracks }) {
2+
return (
3+
<section id="paths" className="section-shell">
4+
<div className="section-heading">
5+
<span className="eyebrow">Choose Your Path</span>
6+
<h2>Different audiences, one shared problem space.</h2>
7+
<p>
8+
Use the lens that fits your work. Each path is curated around a specific
9+
job to be done, not just a list of links.
10+
</p>
11+
</div>
12+
13+
<div className="path-grid">
14+
{tracks.map((track) => {
15+
const isActive = activePath === track.key;
16+
17+
return (
18+
<button
19+
key={track.key}
20+
type="button"
21+
className={`path-card card-lift${isActive ? ' active' : ''}`}
22+
onClick={() => setActivePath(track.key)}
23+
>
24+
<div className="path-card-top">
25+
<span className="path-badge" style={{ background: track.softColor, color: track.color }}>
26+
{track.label}
27+
</span>
28+
<span className="path-kicker" style={{ color: track.color }}>
29+
{track.kicker}
30+
</span>
31+
</div>
32+
33+
<h3>{track.title}</h3>
34+
<p>{track.description}</p>
35+
36+
<ul>
37+
{track.bullets.map((bullet) => (
38+
<li key={bullet}>{bullet}</li>
39+
))}
40+
</ul>
41+
42+
<span className="path-cta" style={{ color: track.color }}>
43+
{isActive ? 'Selected below' : track.cta}
44+
</span>
45+
</button>
46+
);
47+
})}
48+
</div>
49+
</section>
50+
);
51+
}

src/components/WhyRecourse.jsx

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const PRINCIPLES = [
2+
{
3+
title: 'More than explanation',
4+
body: 'Recourse moves beyond telling people why a model said no. It gives them a concrete path toward a better outcome.',
5+
},
6+
{
7+
title: 'Where research meets product',
8+
body: 'Good recourse balances validity, cost, feasibility, and fairness. That makes it a design problem as much as a modeling problem.',
9+
},
10+
{
11+
title: 'Critical for deployment',
12+
body: 'In lending, hiring, insurance, and healthcare, actionable guidance can improve trust, retention, and auditability.',
13+
},
14+
];
15+
16+
export default function WhyRecourse() {
17+
return (
18+
<section id="about" className="section-shell">
19+
<div className="section-heading">
20+
<span className="eyebrow">Why Recourse Matters</span>
21+
<h2>Decision systems need better answers than a rejection.</h2>
22+
<p>
23+
Algorithmic recourse sits at the intersection of responsible AI, product
24+
design, and operational deployment. It asks a simple question: if a model
25+
denies someone access, what realistic action could change that outcome?
26+
</p>
27+
</div>
28+
29+
<div className="why-grid">
30+
<div className="why-frame card-lift">
31+
<div className="why-flow">
32+
<div className="why-step">
33+
<span>01</span>
34+
<strong>Model decision</strong>
35+
<p>A classifier produces an unfavorable result.</p>
36+
</div>
37+
<div className="why-arrow" />
38+
<div className="why-step">
39+
<span>02</span>
40+
<strong>Actionable guidance</strong>
41+
<p>The system identifies feasible, human-understandable changes.</p>
42+
</div>
43+
<div className="why-arrow" />
44+
<div className="why-step">
45+
<span>03</span>
46+
<strong>Institutional value</strong>
47+
<p>Teams improve usability, trust, and governance at the same time.</p>
48+
</div>
49+
</div>
50+
</div>
51+
52+
<div className="why-principles">
53+
{PRINCIPLES.map((item) => (
54+
<article key={item.title} className="principle-card card-lift">
55+
<h3>{item.title}</h3>
56+
<p>{item.body}</p>
57+
</article>
58+
))}
59+
</div>
60+
</div>
61+
</section>
62+
);
63+
}

0 commit comments

Comments
 (0)