Skip to content

Commit 49d2697

Browse files
amirhkclaude
andcommitted
Refactor: modular Vite + React architecture with CI/CD deploy pipeline
Replaces single-file index.html with a fully structured React project: Project scaffold: - package.json with Vite 5, React 18, Recharts dependencies - vite.config.js with code-splitting (react, recharts chunks) - index.html as clean Vite entry point - .gitignore for node_modules, dist, .env files - public/CNAME for custom domain preservation Data layer (src/data/): - papers.js — 16 papers with approach, tags, citations - methods.js — 10 benchmark methods + METRICS_GLOSSARY - ideas.js — 6 idea board items - business.js — BUSINESS_MAP, MOCK_APPLICANT, RECOURSE_PLANS Components (src/components/): - Nav.jsx — sticky frosted-glass navbar - hero/Hero.jsx — headline, persona CTAs, stats bar - hero/HeroPathVisual.jsx — animated SVG blocked→rerouted path - TrackTabs.jsx — mobile tab switcher - Footer.jsx — 4-column footer with newsletter input - tracks/ResearchTrack/index.jsx — search, dual-axis filters, paper list - tracks/ResearchTrack/PaperCard.jsx — per-paper card component - tracks/ResearchTrack/IdeaBoard.jsx — upvote-based idea board - tracks/BuildTrack/index.jsx — shared filter state - tracks/BuildTrack/ApplicabilityMatrix.jsx — Recharts scatter plot - tracks/BuildTrack/PipInstallWizard.jsx — dynamic terminal wizard - tracks/BuildTrack/MetricsGlossary.jsx — glossary cards - tracks/DeployTrack/index.jsx — executive track root - tracks/DeployTrack/BusinessValueMap.jsx — metric→ROI infographic - tracks/DeployTrack/BankManagerDemo.jsx — animated recourse demo CI/CD: - .github/workflows/deploy.yml — GitHub Actions: build → deploy to Pages Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent fa20c4a commit 49d2697

28 files changed

Lines changed: 1311 additions & 975 deletions

.github/workflows/deploy.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: Build and Deploy
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
permissions:
10+
contents: read
11+
pages: write
12+
id-token: write
13+
14+
concurrency:
15+
group: pages
16+
cancel-in-progress: false
17+
18+
jobs:
19+
build:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- name: Checkout
23+
uses: actions/checkout@v4
24+
25+
- name: Setup Node
26+
uses: actions/setup-node@v4
27+
with:
28+
node-version: 20
29+
cache: npm
30+
31+
- name: Install dependencies
32+
run: npm ci
33+
34+
- name: Build
35+
run: npm run build
36+
37+
- name: Upload artifact
38+
uses: actions/upload-pages-artifact@v3
39+
with:
40+
path: dist
41+
42+
deploy:
43+
needs: build
44+
runs-on: ubuntu-latest
45+
if: github.ref == 'refs/heads/main'
46+
environment:
47+
name: github-pages
48+
url: ${{ steps.deployment.outputs.page_url }}
49+
steps:
50+
- name: Deploy to GitHub Pages
51+
id: deployment
52+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
node_modules/
2+
dist/
3+
.DS_Store
4+
*.local
5+
.env
6+
.env.local
7+
.env.production

index.html

Lines changed: 15 additions & 975 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "algorithmicrecourse",
3+
"version": "1.0.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "vite build",
9+
"preview": "vite preview",
10+
"lint": "eslint src --ext .jsx,.js"
11+
},
12+
"dependencies": {
13+
"react": "^18.2.0",
14+
"react-dom": "^18.2.0",
15+
"recharts": "^2.10.3"
16+
},
17+
"devDependencies": {
18+
"@types/react": "^18.2.0",
19+
"@types/react-dom": "^18.2.0",
20+
"@vitejs/plugin-react": "^4.2.1",
21+
"vite": "^5.1.0"
22+
}
23+
}

public/CNAME

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
algorithmicrecourse.com

src/App.jsx

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { useState, useEffect } from 'react';
2+
import Nav from './components/Nav.jsx';
3+
import Hero from './components/hero/Hero.jsx';
4+
import TrackTabs from './components/TrackTabs.jsx';
5+
import ResearchTrack from './components/tracks/ResearchTrack/index.jsx';
6+
import BuildTrack from './components/tracks/BuildTrack/index.jsx';
7+
import DeployTrack from './components/tracks/DeployTrack/index.jsx';
8+
import Footer from './components/Footer.jsx';
9+
10+
export default function App() {
11+
const [activeTab, setActiveTab] = useState('research');
12+
const [isMobile, setIsMobile] = useState(false);
13+
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' },
25+
];
26+
27+
return (
28+
<>
29+
<Nav />
30+
<Hero />
31+
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
37+
</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>
45+
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+
))}
71+
</div>
72+
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>
77+
</div>
78+
)}
79+
</section>
80+
81+
<Footer />
82+
</>
83+
);
84+
}

src/components/Footer.jsx

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
const FOOTER_LINKS = {
2+
Platform: ['Research Papers', 'Benchmark', 'Methods Library', 'Idea Board', 'Open Problems'],
3+
Community: ['GitHub', 'Submit a Paper', 'Submit a Method', 'Mailing List', 'Contact'],
4+
Enterprise:['Partner with Us', 'Schedule an Audit', 'Case Studies', 'Compliance Guide', 'API Access'],
5+
};
6+
7+
export default function Footer() {
8+
const linkStyle = {
9+
display: 'block', fontSize: '.85rem', color: 'rgba(255,255,255,.55)',
10+
textDecoration: 'none', marginBottom: 8, transition: 'color .15s',
11+
};
12+
13+
return (
14+
<footer style={{ background: '#0f172a', padding: '3rem 2rem 2rem', borderTop: '1px solid rgba(255,255,255,.06)' }}>
15+
<div style={{ maxWidth: 1440, margin: '0 auto' }}>
16+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(200px,1fr))', gap: '2.5rem', marginBottom: '2.5rem' }}>
17+
18+
{/* Brand */}
19+
<div>
20+
<div style={{ display: 'flex', alignItems: 'center', gap: 9, marginBottom: '1rem' }}>
21+
<div style={{ width: 30, height: 30, background: 'linear-gradient(135deg,#1e3a8a,#2563eb)', borderRadius: 7, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
22+
<svg width="16" height="16" viewBox="0 0 18 18" fill="none">
23+
<path d="M3 9h4l2-5 2 10 2-5h4" stroke="white" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
24+
</svg>
25+
</div>
26+
<span style={{ fontWeight: 800, color: '#fff', fontSize: '.92rem' }}>AlgorithmicRecourse</span>
27+
</div>
28+
<p style={{ fontSize: '.82rem', color: 'rgba(255,255,255,.45)', lineHeight: 1.65, maxWidth: 240, marginBottom: '1rem' }}>
29+
The comprehensive hub for algorithmic recourse research, tools, and enterprise deployment.
30+
</p>
31+
<p style={{ fontSize: '.75rem', color: 'rgba(255,255,255,.28)', lineHeight: 1.6 }}>
32+
CHARM Lab · University of Waterloo<br />Waterloo, ON, Canada
33+
</p>
34+
</div>
35+
36+
{/* Link columns */}
37+
{Object.entries(FOOTER_LINKS).map(([heading, items]) => (
38+
<div key={heading}>
39+
<div style={{ fontSize: '.68rem', fontWeight: 700, color: 'rgba(255,255,255,.28)', textTransform: 'uppercase', letterSpacing: '.1em', marginBottom: '1rem' }}>
40+
{heading}
41+
</div>
42+
{items.map(l => (
43+
<a key={l} href="#" style={linkStyle}
44+
onMouseEnter={e => e.currentTarget.style.color = '#fff'}
45+
onMouseLeave={e => e.currentTarget.style.color = 'rgba(255,255,255,.55)'}>
46+
{l}
47+
</a>
48+
))}
49+
</div>
50+
))}
51+
52+
{/* Newsletter */}
53+
<div>
54+
<div style={{ fontSize: '.68rem', fontWeight: 700, color: 'rgba(255,255,255,.28)', textTransform: 'uppercase', letterSpacing: '.1em', marginBottom: '1rem' }}>
55+
Stay Updated
56+
</div>
57+
<p style={{ fontSize: '.82rem', color: 'rgba(255,255,255,.45)', lineHeight: 1.65, marginBottom: '1rem' }}>
58+
New papers, benchmarks, and tools — directly to your inbox.
59+
</p>
60+
<div style={{ display: 'flex', gap: 8 }}>
61+
<input type="email" placeholder="you@university.edu"
62+
style={{ flex: 1, padding: '9px 12px', background: 'rgba(255,255,255,.06)', border: '1px solid rgba(255,255,255,.12)', borderRadius: 7, color: '#fff', fontSize: '.82rem', outline: 'none', fontFamily: 'inherit' }} />
63+
<button style={{ padding: '9px 16px', background: '#2563eb', color: '#fff', borderRadius: 7, fontWeight: 700, fontSize: '.85rem', border: 'none', cursor: 'pointer', fontFamily: 'inherit' }}></button>
64+
</div>
65+
</div>
66+
</div>
67+
68+
{/* Bottom bar */}
69+
<div style={{ borderTop: '1px solid rgba(255,255,255,.08)', paddingTop: '1.5rem', display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '1rem' }}>
70+
<span style={{ fontSize: '.75rem', color: 'rgba(255,255,255,.3)' }}>
71+
© 2025 CHARM Lab, University of Waterloo. All rights reserved.
72+
</span>
73+
<div style={{ display: 'flex', gap: '1.5rem' }}>
74+
{['Privacy', 'Terms', 'Accessibility'].map(l => (
75+
<a key={l} href="#" style={{ fontSize: '.75rem', color: 'rgba(255,255,255,.3)', textDecoration: 'none', transition: 'color .15s' }}
76+
onMouseEnter={e => e.currentTarget.style.color = 'rgba(255,255,255,.7)'}
77+
onMouseLeave={e => e.currentTarget.style.color = 'rgba(255,255,255,.3)'}>
78+
{l}
79+
</a>
80+
))}
81+
</div>
82+
</div>
83+
</div>
84+
</footer>
85+
);
86+
}

src/components/Nav.jsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { useState, useEffect } from 'react';
2+
3+
export default function Nav() {
4+
const [scrolled, setScrolled] = useState(false);
5+
6+
useEffect(() => {
7+
const fn = () => setScrolled(window.scrollY > 40);
8+
window.addEventListener('scroll', fn);
9+
return () => window.removeEventListener('scroll', fn);
10+
}, []);
11+
12+
const links = ['Research', 'Build', 'Deploy', 'About'];
13+
14+
return (
15+
<nav style={{
16+
position: 'fixed', top: 0, left: 0, right: 0, zIndex: 200, height: 64,
17+
background: scrolled ? 'rgba(255,255,255,.97)' : 'rgba(255,255,255,.6)',
18+
backdropFilter: 'blur(16px)',
19+
borderBottom: scrolled ? '1px solid #e2e8f0' : '1px solid transparent',
20+
transition: 'all .3s', padding: '0 2rem',
21+
}}>
22+
<div style={{ maxWidth: 1440, margin: '0 auto', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
23+
24+
{/* Logo */}
25+
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
26+
<div style={{ width: 32, height: 32, background: 'linear-gradient(135deg,#1e3a8a,#2563eb)', borderRadius: 8, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
27+
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
28+
<path d="M3 9h4l2-5 2 10 2-5h4" stroke="white" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
29+
</svg>
30+
</div>
31+
<span style={{ fontWeight: 800, fontSize: '.95rem', color: '#0f172a', letterSpacing: '-.02em' }}>
32+
AlgorithmicRecourse
33+
</span>
34+
</div>
35+
36+
{/* Links */}
37+
<div style={{ display: 'flex', alignItems: 'center', gap: '1.75rem' }}>
38+
{links.map(l => (
39+
<a key={l} href={`#${l.toLowerCase()}`}
40+
style={{ fontSize: '.85rem', fontWeight: 600, color: '#475569', textDecoration: 'none', transition: 'color .15s' }}
41+
onMouseEnter={e => e.currentTarget.style.color = '#0f172a'}
42+
onMouseLeave={e => e.currentTarget.style.color = '#475569'}>
43+
{l}
44+
</a>
45+
))}
46+
<a href="https://github.com/charmlab" target="_blank" rel="noopener noreferrer"
47+
style={{ padding: '8px 18px', background: '#0f172a', color: '#fff', borderRadius: 8, fontSize: '.85rem', fontWeight: 700, textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 6, transition: 'background .2s' }}
48+
onMouseEnter={e => e.currentTarget.style.background = '#1e3a8a'}
49+
onMouseLeave={e => e.currentTarget.style.background = '#0f172a'}>
50+
<GitHubIcon />
51+
GitHub
52+
</a>
53+
</div>
54+
</div>
55+
</nav>
56+
);
57+
}
58+
59+
function GitHubIcon() {
60+
return (
61+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
62+
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" />
63+
</svg>
64+
);
65+
}

src/components/TrackTabs.jsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const TABS = [
2+
{ key: 'research', label: 'Explore Research', icon: '🔬' },
3+
{ key: 'build', label: 'Build with Recourse', icon: '⚙' },
4+
{ key: 'deploy', label: 'Deploy', icon: '🏢' },
5+
];
6+
7+
export default function TrackTabs({ active, setActive }) {
8+
return (
9+
<div style={{
10+
display: 'flex', gap: 8, justifyContent: 'center',
11+
padding: '1rem', background: '#f8fafc',
12+
borderBottom: '1px solid #e2e8f0',
13+
position: 'sticky', top: 64, zIndex: 100,
14+
flexWrap: 'wrap',
15+
}}>
16+
{TABS.map(t => (
17+
<button
18+
key={t.key}
19+
onClick={() => setActive(t.key)}
20+
style={{
21+
padding: '10px 20px', borderRadius: 10, fontWeight: 700, fontSize: '.82rem',
22+
cursor: 'pointer', fontFamily: 'inherit',
23+
border: `1.5px solid ${active === t.key ? '#0f172a' : '#e2e8f0'}`,
24+
background: active === t.key ? '#0f172a' : '#fff',
25+
color: active === t.key ? '#fff' : '#475569',
26+
boxShadow: active === t.key ? '0 4px 16px rgba(15,23,42,.2)' : 'none',
27+
transition: 'all .2s',
28+
}}
29+
>
30+
<span style={{ marginRight: 6 }}>{t.icon}</span>
31+
{t.label}
32+
</button>
33+
))}
34+
</div>
35+
);
36+
}

0 commit comments

Comments
 (0)