Skip to content

Commit fded516

Browse files
committed
第一个版本
0 parents  commit fded516

File tree

60 files changed

+7247
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+7247
-0
lines changed

.eslintrc.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"extends": ["next/core-web-vitals"]
3+
}
4+
5+

.github/workflows/deploy.yml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: [ main, master ]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: "pages"
15+
cancel-in-progress: true
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v4
23+
24+
- name: Setup Node
25+
uses: actions/setup-node@v4
26+
with:
27+
node-version: '20'
28+
cache: 'npm'
29+
30+
- name: Install dependencies
31+
run: npm ci
32+
33+
- name: Build (Static Export)
34+
run: npm run build
35+
env:
36+
NEXT_PUBLIC_BASE_PATH: /people/wang_hao
37+
38+
- name: Re-root export under basePath
39+
run: |
40+
rm -rf out_tmp
41+
mv out out_tmp
42+
mkdir -p out/people/wang_hao
43+
cp -r out_tmp/* out/people/wang_hao/
44+
# keep .nojekyll at root and subdir for safety
45+
cp -f out_tmp/.nojekyll out/.nojekyll || true
46+
cp -f out_tmp/.nojekyll out/people/wang_hao/.nojekyll || true
47+
48+
- name: Upload artifact
49+
uses: actions/upload-pages-artifact@v3
50+
with:
51+
path: out
52+
53+
deploy:
54+
environment:
55+
name: github-pages
56+
url: ${{ steps.deployment.outputs.page_url }}
57+
runs-on: ubuntu-latest
58+
needs: build
59+
steps:
60+
- name: Deploy to GitHub Pages
61+
id: deployment
62+
uses: actions/deploy-pages@v4
63+
64+

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
node_modules
2+
.next
3+
out
4+
build
5+
.DS_Store
6+
7+

app/components/LangSwitch.tsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"use client";
2+
3+
import Link from 'next/link';
4+
import { usePathname } from 'next/navigation';
5+
import { useEffect, useRef, useState } from 'react';
6+
import Image from 'next/image';
7+
export default function LangSwitch() {
8+
const pathname = usePathname();
9+
const current = pathname.startsWith('/en') ? 'en' : pathname.startsWith('/ja') ? 'ja' : 'zh';
10+
const routes = { zh: '/', en: '/en', ja: '/ja' } as const;
11+
12+
const [open, setOpen] = useState(false);
13+
const btnRef = useRef<HTMLButtonElement | null>(null);
14+
const menuRef = useRef<HTMLDivElement | null>(null);
15+
16+
useEffect(() => {
17+
function onDocClick(e: MouseEvent) {
18+
if (!open) return;
19+
const target = e.target as Node;
20+
if (menuRef.current && !menuRef.current.contains(target) && btnRef.current && !btnRef.current.contains(target)) {
21+
setOpen(false);
22+
}
23+
}
24+
function onKey(e: KeyboardEvent) {
25+
if (e.key === 'Escape') setOpen(false);
26+
}
27+
document.addEventListener('click', onDocClick);
28+
document.addEventListener('keydown', onKey);
29+
return () => {
30+
document.removeEventListener('click', onDocClick);
31+
document.removeEventListener('keydown', onKey);
32+
};
33+
}, [open]);
34+
35+
const currentLabel = current === 'zh' ? '中文' : current === 'en' ? 'EN' : '日本語';
36+
37+
return (
38+
<div className="lang-dropdown">
39+
40+
<button ref={btnRef} className="btn btn-outline btn-inline" aria-haspopup="menu" aria-expanded={open} onClick={() => setOpen(v => !v)}>
41+
<Image src="/language-svgrepo-com.svg" alt="Cogling-AI Lab" width={20} height={20} />
42+
{currentLabel}
43+
</button>
44+
{open && (
45+
<div ref={menuRef} className="lang-menu" role="menu">
46+
47+
<Link role="menuitem" href={routes.zh} aria-current={current === 'zh' ? 'page' : undefined} onClick={() => setOpen(false)}>中文</Link>
48+
<Link role="menuitem" href={routes.en} aria-current={current === 'en' ? 'page' : undefined} onClick={() => setOpen(false)}>EN</Link>
49+
<Link role="menuitem" href={routes.ja} aria-current={current === 'ja' ? 'page' : undefined} onClick={() => setOpen(false)}>日本語</Link>
50+
</div>
51+
)}
52+
</div>
53+
);
54+
}
55+
56+

app/components/SideToc.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"use client";
2+
3+
import { useEffect, useState } from 'react';
4+
5+
type TocItem = { id: string; label: string };
6+
7+
export default function SideToc({ items }: { items: TocItem[] }) {
8+
const [active, setActive] = useState<string | null>(null);
9+
10+
useEffect(() => {
11+
const observers: IntersectionObserver[] = [];
12+
items.forEach(({ id }) => {
13+
const el = document.getElementById(id);
14+
if (!el) return;
15+
const ob = new IntersectionObserver(
16+
entries => {
17+
entries.forEach(e => {
18+
if (e.isIntersecting) setActive(id);
19+
});
20+
},
21+
{ root: null, rootMargin: "-40% 0px -55% 0px", threshold: [0, 0.25, 0.5, 0.75, 1] }
22+
);
23+
ob.observe(el);
24+
observers.push(ob);
25+
});
26+
return () => observers.forEach(o => o.disconnect());
27+
}, [items]);
28+
29+
return (
30+
<nav className="side-toc" aria-label="Section Navigation">
31+
{items.map(({ id, label }) => (
32+
<a key={id} href={`#${id}`} aria-current={active === id ? 'page' : undefined}>
33+
{label}
34+
</a>
35+
))}
36+
</nav>
37+
);
38+
}
39+
40+

app/en/page.tsx

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import LangSwitch from '../components/LangSwitch';
2+
import SideToc from '../components/SideToc';
3+
import Link from 'next/link';
4+
import { getDict } from '../lib/i18n';
5+
6+
export default function HomePageEN() {
7+
const t = getDict('en');
8+
return (
9+
<main>
10+
<header className="site-header">
11+
<div className="container header-inner">
12+
<div className="brand"><Link href="/">{t.brand}</Link></div>
13+
<div style={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
14+
<nav className="nav">
15+
<a href="#home">{t.nav.home}</a>
16+
<a href="#publications">{t.nav.publications}</a>
17+
<a href="#research">{t.nav.research}</a>
18+
<a href="#courses">{t.nav.courses}</a>
19+
</nav>
20+
<LangSwitch />
21+
</div>
22+
</div>
23+
</header>
24+
25+
<section id="home" className="hero">
26+
<div className="container">
27+
<h1 className="title">{t.hero.title}</h1>
28+
<p className="subtitle">{t.hero.subtitle}</p>
29+
<div className="hero-actions">
30+
<a className="btn btn-primary" href="#about">{t.hero.ctaLearn}</a>
31+
<a className="btn btn-outline" href={`mailto:${t.contact.email}`}>{t.hero.ctaEmail}</a>
32+
</div>
33+
<div className="profile-card">
34+
<h2>{t.profile.name}</h2>
35+
<div className="lines">
36+
{t.profile.lines.map((line: string, idx: number) => (<div key={idx}>{line}</div>))}
37+
</div>
38+
</div>
39+
</div>
40+
</section>
41+
42+
<section id="publications" className="section">
43+
<div className="container">
44+
<h2 className="section-title">{t.publications.title}</h2>
45+
<ul className="list">
46+
{t.publications.list.map((p: any, idx: number) => (
47+
<li key={idx}>
48+
{p.href ? (<a href={p.href} target="_blank" rel="noopener noreferrer">{p.text}</a>) : p.text}
49+
</li>
50+
))}
51+
</ul>
52+
</div>
53+
</section>
54+
55+
<section id="about" className="section">
56+
<div className="container">
57+
<h2 className="section-title">{t.about.title}</h2>
58+
<p>{t.about.p1}</p>
59+
<p>{t.about.p2}</p>
60+
</div>
61+
</section>
62+
63+
<section id="courses" className="section">
64+
<div className="container">
65+
<h2 className="section-title">{t.courses.title}</h2>
66+
<ul className="list">
67+
{t.courses.list.map((item, idx) => (
68+
<li key={idx}>
69+
{item.href ? (
70+
<a href={item.href} target="_blank" rel="noopener noreferrer">{item.text}</a>
71+
) : (
72+
item.text
73+
)}
74+
</li>
75+
))}
76+
</ul>
77+
</div>
78+
</section>
79+
80+
<section id="research" className="section">
81+
<div className="container">
82+
<h2 className="section-title">{t.research.title}</h2>
83+
<ul className="list">
84+
{t.research.bullets.map((b, idx) => (<li key={idx}>{b}</li>))}
85+
</ul>
86+
<p>
87+
{t.research.notePrefix} <a className="link-strong" href={`mailto:${t.contact.email}`}>{t.research.contactCta}</a>{t.research.noteSuffix}
88+
</p>
89+
</div>
90+
</section>
91+
92+
<section id="expectations" className="section">
93+
<div className="container">
94+
<h2 className="section-title">{t.expectations.title}</h2>
95+
<ul className="list">
96+
{t.expectations.list.map((b, idx) => (<li key={idx}>{b}</li>))}
97+
</ul>
98+
</div>
99+
</section>
100+
101+
<section id="insights" className="section">
102+
<div className="container">
103+
<h2 className="section-title">{t.insights.title}</h2>
104+
<blockquote className="quote">
105+
<p>
106+
{t.insights.quoteLines[0]}<br />
107+
{t.insights.quoteLines[1]}
108+
</p>
109+
</blockquote>
110+
</div>
111+
</section>
112+
113+
<section id="students" className="section">
114+
<div className="container">
115+
<h2 className="section-title">{t.students.title}</h2>
116+
<div className="table-wrap">
117+
<table className="table">
118+
<thead>
119+
<tr>
120+
{t.students.columns.map((c, idx) => (<th key={idx}>{c}</th>))}
121+
</tr>
122+
</thead>
123+
<tbody>
124+
{t.students.rows.map((r, idx) => (
125+
<tr key={idx}>
126+
{r.map((cell, cidx) => (<td key={cidx}>{cell}</td>))}
127+
</tr>
128+
))}
129+
</tbody>
130+
</table>
131+
</div>
132+
</div>
133+
</section>
134+
135+
<section id="alumni" className="section">
136+
<div className="container">
137+
<h2 className="section-title">{t.alumni.title}</h2>
138+
<div className="table-wrap">
139+
<table className="table">
140+
<thead>
141+
<tr>
142+
{t.alumni.columns.map((c, idx) => (<th key={idx}>{c}</th>))}
143+
</tr>
144+
</thead>
145+
<tbody>
146+
{t.alumni.rows.map((r, idx) => (
147+
<tr key={idx}>
148+
{r.map((cell, cidx) => (<td key={cidx}>{cell}</td>))}
149+
</tr>
150+
))}
151+
</tbody>
152+
</table>
153+
</div>
154+
</div>
155+
</section>
156+
157+
<section id="contact" className="section">
158+
<div className="container">
159+
<h2 className="section-title">{t.contact.title}</h2>
160+
<p>
161+
<strong>{t.contact.emailLabel}</strong>: <a className="link-strong" href={`mailto:${t.contact.email}`}>{t.contact.email}</a>
162+
</p>
163+
</div>
164+
</section>
165+
166+
<footer className="site-footer">
167+
<div className="container">
168+
<p>{t.footer.replace('{year}', String(new Date().getFullYear()))}</p>
169+
</div>
170+
</footer>
171+
172+
<SideToc items={[
173+
{ id: 'home', label: t.nav.home },
174+
{ id: 'publications', label: t.nav.publications },
175+
{ id: 'research', label: t.nav.research },
176+
{ id: 'courses', label: t.nav.courses }
177+
]} />
178+
</main>
179+
);
180+
}
181+
182+

0 commit comments

Comments
 (0)