Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c6549dd
fix: sync About page with latest LinkedIn profile
kaiiiichen Jul 10, 2026
26153e8
fix: remove BlueDot Impact from Focus card
kaiiiichen Jul 10, 2026
f421560
feat: add coursework to Education entries on About page
kaiiiichen Jul 10, 2026
9c30486
feat: show coursework in a hover panel like last month's tops
kaiiiichen Jul 10, 2026
742c7cc
feat: full coursework list without scrolling and LinkedIn-aligned dates
kaiiiichen Jul 10, 2026
208b7c9
feat: about page polish — no-wrap coursework, focus links, one-line i…
kaiiiichen Jul 10, 2026
96c7593
feat: neutral monochrome theme, instant theme switch, mobile coursewo…
kaiiiichen Jul 10, 2026
6478f91
feat: pure white / pure black theme extremes
kaiiiichen Jul 10, 2026
fdf3f4d
feat: crisp black/white hover accents instead of mid-gray
kaiiiichen Jul 10, 2026
b909343
perf: snappier page-to-page transitions
kaiiiichen Jul 10, 2026
95d95a0
feat: remove page intros and entry animations for instant loads
kaiiiichen Jul 10, 2026
13e35e0
feat: move Misc note to footnote and drop GitHub Activity section
kaiiiichen Jul 10, 2026
eca9122
feat: hand-maintained project list with live star counts
kaiiiichen Jul 10, 2026
0333fc9
fix: restore visible row hover background on pure-white cards
kaiiiichen Jul 10, 2026
c3d8351
perf: remove nav link color transitions
kaiiiichen Jul 10, 2026
2a2d12b
feat: add four learning resources to Misc
kaiiiichen Jul 10, 2026
80fea26
feat: add AI 2027 to Watching and two resources to Misc
kaiiiichen Jul 10, 2026
c1b8d42
fix: point course notes card at the full Notion page URL
kaiiiichen Jul 10, 2026
2edb189
fix: unify section spacing across all subpages
kaiiiichen Jul 10, 2026
bf3306d
feat: monochrome KTC favicon and manual project order
kaiiiichen Jul 10, 2026
e64fec2
feat: set favicon KTC in Nunito, outlined as vector paths
kaiiiichen Jul 10, 2026
7052e1f
fix: point SudoSodoku at its new SudoSodokuApp org repo
kaiiiichen Jul 10, 2026
0ebfc2e
fix: sort Watching alphabetically per the Misc page rule
kaiiiichen Jul 10, 2026
b62d618
feat: reorder Misc sections
kaiiiichen Jul 10, 2026
f5ec0ed
feat: drop Signal contact and help icon; reword Misc footnote
kaiiiichen Jul 10, 2026
0d6670a
fix: cancel pending open rAF in HoverTip so fast sweeps can't strand …
kaiiiichen Jul 10, 2026
9c2cb56
feat: linkable coursework entries on the About page
kaiiiichen Jul 10, 2026
dc889f0
feat: use Kai T. Chen everywhere instead of Kai Thomas Chen
kaiiiichen Jul 10, 2026
9dd0052
feat: remove the View projects chip from the Oxford education entry
kaiiiichen Jul 10, 2026
8e627cc
chore: final consistency pass
kaiiiichen Jul 10, 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
3 changes: 1 addition & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,14 @@ Config: [`app/lib/spotify-playlists.ts`](app/lib/spotify-playlists.ts) (`CURATED
- **Mount:** [`app/components/subpage-enter.tsx`](app/components/subpage-enter.tsx) — portaled to `document.body`, `position: fixed` (does not affect layout flow)
- **Discovery order:** explicit `TocSection` (`data-toc-item`) → `.mag-label` → `.mag-card h1/h2`
- **Shown when:** ≥2 entries, viewport gutter ≥28px right of content column, not on excluded paths (`/music-for-life/map`)
- **Home `/`:** `TocSection` labels — Kai Thomas Chen, Greeting, Contact; `.mag-label` — Listening, Location, Projects. Identity row uses **`md:flex-row-reverse`** + mobile `order-*` so TOC DOM order matches reading order **without** CSS Grid row-height gaps under the social column.
- **Home `/`:** `TocSection` labels — Kai T. Chen, Greeting, Contact; `.mag-label` — Listening, Location, Projects. Identity row uses **`md:flex-row-reverse`** + mobile `order-*` so TOC DOM order matches reading order **without** CSS Grid row-height gaps under the social column.
- **Playlist pages:** index = Music for Life + each playlist name (`level={1}`); detail = playlist name + **Tracks** — never one TOC entry per song.

### Performance / loading (recent)

- **Fonts:** Nunito via [`app/fonts.ts`](app/fonts.ts) (`next/font/google`); no `@fontsource` in client bundle
- **Weather:** Berkeley weather SSR on home via [`app/lib/weather.ts`](app/lib/weather.ts); `WeatherCard` accepts `initialWeather`
- **Mapbox:** dynamic import in [`app/components/playlist-map-loader.tsx`](app/components/playlist-map-loader.tsx)
- **GitHub heatmap:** prefetch on `/projects` via [`app/lib/github-contributions.ts`](app/lib/github-contributions.ts)
- **Route loading UI:** `app/music-for-life/loading.tsx`, `app/music-for-life/[id]/loading.tsx`

---
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ Canonical documentation: **[README.md](README.md)** (setup, routes, APIs, env, C
| **Now playing** | `GET /api/spotify/now-playing` — Spotify Web API; optional Supabase writes (`listening_*`) with service role |
| **Last month top** | `GET /api/spotify/last-month-top` — Spotify top tracks (`short_term`, needs `user-top-read`) |
| **Client polling** | `app/hooks/use-now-playing.ts` — polls every **10s** (`cache: "no-store"`) |
| **GitHub** | `GET /api/github/contributions` — GraphQL calendar; pinned repos in `app/lib/github-pinned.ts` |
| **GitHub** | `GET /api/github/contributions` — GraphQL calendar; hand-maintained project list in `app/lib/personal-projects.ts` (stars fetched live) |
| **Weather** | `GET /api/weather` — Open-Meteo (Berkeley); `berkeley-time.tsx` |
| **Home UI** | Identity + social links; **Listening** / **Location** cards; **Projects** outer card with nested Course + Personal (`projects-split.tsx`); **PageToc** (Kai Thomas Chen, Greeting, Contact, Listening, Location, Projects) |
| **Home UI** | Identity + social links; **Listening** / **Location** cards; **Projects** outer card with nested Course + Personal (`projects-split.tsx`); **PageToc** (Kai T. Chen, Greeting, Contact, Listening, Location, Projects) |
| **Music for Life** | `/music-for-life` — curated Spotify playlists ([`MUSIC_FOR_LIFE_BASE`](lib/spotify-playlists.ts)); `[id]` detail; `/map` Mapbox viewer; old `/playlists` → 301 redirect |
| **Page TOC** | [`page-toc.tsx`](app/components/page-toc.tsx) + [`toc-section.tsx`](app/components/toc-section.tsx) — fixed right rail on `/`, `/about`, `/projects`, `/misc`, `/music-for-life` (not map); auto `.mag-label` + explicit `TocSection` |
| **Course Projects** | `app/lib/course-projects.ts` — Oxford DUL (`/projects/oxford-dul-2025`) + Notion notes |
Expand Down
38 changes: 19 additions & 19 deletions README.md

Large diffs are not rendered by default.

151 changes: 93 additions & 58 deletions app/about/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Metadata } from "next";
import MagChip from "../components/mag-chip";
import CourseworkTip from "../components/coursework-tip";
import { HoverLinkArrow, HoverLinkDestinationHint } from "../components/hover-link-hint";

type AboutRole = {
years: string;
Expand Down Expand Up @@ -27,16 +28,6 @@ export default function About() {
</h1>
</div>

{/* Personality / side */}
<div className="fade-up" style={{ animationDelay: "30ms" }}>
<div style={{ fontFamily: "var(--font-nunito)", fontWeight: 400, fontSize: 17, lineHeight: 1.75 }} className="text-zinc-400 dark:text-zinc-600 mt-3">
<p>I study Maths for my bachelor&apos;s degree and I&apos;m always awed by the beauty of it (as the motivation to learn it through so much hard work)! I love the outline of analysis proofs and I&apos;m especially obsessed with algebra structures.</p>
<p>I also learn some computer science and data science, and I find myself better at them than Maths lol. I&apos;m drawn to the problem solving, ideas behind product design, and the empathy I feel when I&apos;m building something for humans.</p>
<p className="mt-1">I really love tinkering stuff. I dream of building something that can help people / make people happy with a fantastic user experience! I also dream of building a tool that could change people&apos;s lives for the better.</p>
<p className="mt-1">In the future, I hope to be able to make my transition to computer science and AI, since I find they could help me to build more things that can help people.</p>
</div>
</div>

{/* Two-column: Education (left) | Experience + Volunteering (right) */}
<div className="grid grid-cols-1 md:grid-cols-2 md:items-stretch gap-6 fade-up" style={{ animationDelay: "60ms" }}>

Expand All @@ -47,34 +38,61 @@ export default function About() {
<div className="flex flex-col md:flex-1 md:justify-between">
{[
{
years: "Sep 2023 – Jun 2027",
years: "2023 – 2027",
institution: "Southern University of Science and Technology",
role: "Bachelor of Science",
subtitle: "Mathematics",
grade: "3.73/4.00 (89.96/100)",
coursework: [
"MA101 Mathematical Analysis I",
"MA107 Advanced Linear Algebra I",
"MA121 Advanced Linear Algebra II (H)",
"MA122 Mathematical Analysis II (H)",
"MA204 Mathematical Statistics",
"MA215 Probability Theory",
"MA219 Abstract Algebra (H)",
"MA230 Ordinary Differential Equations A (H)",
"MA231 Mathematical Analysis III (H)",
"MA232 Complex Analysis (H)",
"MA234 Introduction to Big Data Analysis",
"MA305 Numerical Analysis",
"MA336 Partial Differential Equations (H)",
"MA337 Real Analysis (H)",
"MA340 Fourier Analysis",
"MAT7092 Stochastic Processes",
"CS108 Introduction to Mathematical Logic (H)",
"CS109 Introduction to Computer Programming",
"CS201 Discrete Mathematics",
"CS217 Data Structures and Algorithm Analysis (H)",
],
},
{
years: "Jan 2026 – Dec 2026",
years: "2026 – 2026",
institution: "University of California, Berkeley",
role: "Visiting Student",
subtitle: "Computer Science and Data Science",
grade: "4.00/4.00 (100/100)",
coursework: [
{ name: "COMPSCI 61A", href: "https://inst.eecs.berkeley.edu/~cs61a/sp26/" },
{ name: "DATA C100", href: "https://ds100.org/sp26/" },
],
},
{
years: "Jun 2025 – Jul 2025",
years: "2025 – 2025",
institution: "University of Oxford",
role: "Summer Visiting Student",
subtitle: "Deep Unsupervised Learning",
grade: "First Class (A+)",
projectHref: "/projects/oxford-dul-2025",
coursework: [
{ name: "Deep Unsupervised Learning", href: "/projects/oxford-dul-2025" },
],
},
{
years: "Jul 2020 – Jun 2023",
years: "2020 – 2023",
institution: "Tongzhou Senior High School of Jiangsu Province",
role: "High School Diploma",
content: "Top 0.42% in Gaokao (1848th in 445k+ in Jiangsu Province)",
},
].map(({ years, institution, role, subtitle, content, grade, projectHref }, i) => (
].map(({ years, institution, role, subtitle, content, grade, coursework }, i) => (
<div
key={i}
className="py-4 md:py-3 border-b border-zinc-100 dark:border-zinc-800/60 last:border-0"
Expand All @@ -93,30 +111,30 @@ export default function About() {
</p>
<p
style={{ fontFamily: "var(--font-nunito)", fontWeight: 600, fontSize: 17, lineHeight: 1.4 }}
className="text-[#C4894F] dark:text-[#D9A870] mt-1"
className="text-zinc-500 dark:text-zinc-400 mt-1"
>
{role}
</p>
{subtitle ? (
<p
style={{ fontFamily: "var(--font-nunito)", fontWeight: 600, fontSize: 17, lineHeight: 1.4 }}
className="text-[#C4894F] dark:text-[#D9A870] mt-0.5"
className="text-zinc-500 dark:text-zinc-400 mt-0.5"
>
{subtitle}
</p>
) : null}
{content || grade ? (
<p
style={{ fontFamily: "var(--font-nunito)", fontWeight: 400, fontSize: 16, lineHeight: 1.5 }}
className="text-[#C4894F] dark:text-[#D9A870] mt-0.5"
className="text-zinc-500 dark:text-zinc-400 mt-0.5"
>
{[content, grade].filter(Boolean).join(" · ")}
</p>
) : null}
{projectHref ? (
<MagChip href={projectHref} arrow="external" className="mt-2">
View projects
</MagChip>
{coursework ? (
<div className="mt-2">
<CourseworkTip courses={coursework} />
</div>
) : null}
</div>
))}
Expand All @@ -127,27 +145,44 @@ export default function About() {
<div className="mag-label">Focus</div>
<div>
{[
{ term: "Summer 26", code: "COMPSCI 61B: Data Structures" },
{ term: "Summer 26", code: "COMPSCI 188: Introduction to Artificial Intelligence" },
{ term: "Summer 26", code: "BlueDot Impact: Future of AI" },
].map(({ term, code }) => (
<div
{
term: "Summer 26",
code: "COMPSCI 61BL: Data Structures and Programming Methodology",
href: "https://cs61bl.org/",
},
{
term: "Summer 26",
code: "COMPSCI 188: Introduction to Artificial Intelligence",
href: "https://inst.eecs.berkeley.edu/~cs188/su26/",
},
].map(({ term, code, href }) => (
<a
key={code}
className="py-4 border-b border-zinc-100 dark:border-zinc-800/60 last:border-0"
href={href}
target="_blank"
rel="noopener noreferrer"
className="group block py-4 -mx-2 px-2 rounded-sm hover:bg-zinc-100 dark:hover:bg-zinc-700/40 transition-all duration-150 no-underline border-b border-zinc-100 dark:border-zinc-800/60 last:border-0"
style={{ textDecoration: "none" }}
>
<span
style={{ fontFamily: "var(--font-nunito)", fontWeight: 400, fontSize: 11, letterSpacing: "0.04em" }}
className="text-zinc-400 dark:text-zinc-600 block mb-1"
>
{term}
</span>
<p
style={{ fontFamily: "var(--font-nunito)", fontWeight: 600, fontSize: 17, lineHeight: 1.4 }}
className="text-zinc-800 dark:text-zinc-200"
>
{code}
</p>
</div>
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-2 min-w-0">
<HoverLinkArrow />
<p
style={{ fontFamily: "var(--font-nunito)", fontWeight: 600, fontSize: 17, lineHeight: 1.4 }}
className="text-zinc-800 dark:text-zinc-200 group-hover:text-black dark:group-hover:text-white transition-colors duration-150"
>
{code}
</p>
</div>
<HoverLinkDestinationHint href={href} label="Course site" />
</div>
</a>
))}
</div>
</div>
Expand Down Expand Up @@ -176,28 +211,12 @@ export default function About() {
detail: "Control theory RA at NCC Lab.",
},
{
years: "Nov 2023 – Jan 2024",
years: "Oct 2023 – Jan 2024",
role: "Teaching Assistant",
detail: "TA for a short-term mindfulness course.",
},
],
},
{
org: "SUSTech Psychology Society (SUSPsych)",
meta: "Shenzhen",
roles: [
{
years: "Jun 2024 – Jun 2025",
role: "President",
detail:
"Organizing mental health workshops and peer support programs in partnership with SUSTech Counseling & Psychological Services.",
},
{
years: "Oct 2023 – Jun 2024",
role: "External Vice President",
},
],
},
{
org: "Lingnan University",
meta: "Hong Kong SAR",
Expand Down Expand Up @@ -241,7 +260,7 @@ export default function About() {
>
<p
style={{ fontFamily: "var(--font-nunito)", fontWeight: 600, fontSize: 17, lineHeight: 1.4 }}
className="text-[#C4894F] dark:text-[#D9A870]"
className="text-zinc-500 dark:text-zinc-400"
>
{roleItem.role}
</p>
Expand Down Expand Up @@ -282,11 +301,27 @@ export default function About() {
meta: "Shenzhen",
roles: [
{
years: "Oct 2023 – Dec 2025",
years: "2023 – 2025",
role: "Certified First Aider",
},
] as AboutRole[],
},
{
org: "SUSTech Psychology Society (SUSPsych)",
meta: "Shenzhen",
roles: [
{
years: "2024 – 2025",
role: "President",
detail:
"Organizing mental health workshops and peer support programs in partnership with SUSTech Counseling & Psychological Services.",
},
{
years: "2023 – 2024",
role: "External Vice President",
},
] as AboutRole[],
},
].map(({ org, meta: orgMeta, roles }, groupIndex) => (
<div
key={org}
Expand Down Expand Up @@ -318,7 +353,7 @@ export default function About() {
>
<p
style={{ fontFamily: "var(--font-nunito)", fontWeight: 600, fontSize: 17, lineHeight: 1.4 }}
className="text-[#C4894F] dark:text-[#D9A870]"
className="text-zinc-500 dark:text-zinc-400"
>
{roleItem.role}
</p>
Expand Down
Loading