diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index def0ed9..091c73a 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -32,7 +32,7 @@ body: - "Not sure" - "Frontend / UI" - "API routes" - - "Content / MDX" + - "Content / data" - "Docs / DX" - "CI / tooling" - "Other" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ee9b6b9..30271ec 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -32,7 +32,7 @@ ## Checklist -- [ ] I have read [CONTRIBUTING.md](CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). +- [ ] I have read **[CONTRIBUTING.md](../CONTRIBUTING.md)** and **[CODE_OF_CONDUCT.md](../CODE_OF_CONDUCT.md)**. - [ ] No secrets, `.env.local`, or production tokens are included in this PR. - [ ] **[README.md](../README.md)** / **[`.env.example`](../.env.example)** updated if routes, APIs, user-facing behavior, or env vars changed. - [ ] **[CLAUDE.md](../CLAUDE.md)** updated if architecture, main data flows, or important paths changed (keeps AI / editor context aligned with README). diff --git a/AGENTS.md b/AGENTS.md index 512ac7e..6a612d5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -116,7 +116,7 @@ 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 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. +- **Home `/`:** `TocSection` labels — Kai T. Chen, Greeting, Contact; `.mag-label` — Listening, Location. Identity row uses a responsive **CSS Grid** (`md:grid-cols-[36%_1fr]`) plus mobile `order-*`; the intro stays first in DOM so TOC order matches reading order while the photo/social column renders first on mobile. - **Playlist pages:** index = Music for Life + each playlist name (`level={1}`); detail = playlist name + **Tracks** — never one TOC entry per song. ### Performance / loading (recent) @@ -136,4 +136,3 @@ Config: [`app/lib/spotify-playlists.ts`](app/lib/spotify-playlists.ts) (`CURATED | Human contribution process | [CONTRIBUTING.md](CONTRIBUTING.md) | | Short Claude Code stack summary | [CLAUDE.md](CLAUDE.md) | | Security reporting | [SECURITY.md](SECURITY.md) | -| Cursor IDE git reminder | [`.cursor/rules/git-workflow.mdc`](.cursor/rules/git-workflow.mdc) | diff --git a/CLAUDE.md b/CLAUDE.md index efb0f44..47f2a87 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,18 +19,19 @@ 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; 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 T. Chen, Greeting, Contact, Listening, Location, Projects) | +| **GitHub** | `GET /api/github/contributions` — GraphQL calendar endpoint; hand-maintained course/personal lists merge descriptions, topics, stars, and archive state from GitHub | +| **Weather** | `app/lib/weather.ts` fetches Open-Meteo for home SSR; `GET /api/weather` mirrors it; `berkeley-time.tsx` renders local time | +| **Home UI** | Identity + Email/GitHub/LinkedIn/X links; **Listening** / server-rendered **Location** cards; **PageToc** (Kai T. Chen, Greeting, Contact, Listening, Location) | | **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 | +| **Course Projects** | `app/lib/course-projects.ts` — Kai Course Notes entry backed by GitHub metadata; Oxford DUL remains at `/projects/oxford-dul-2025` | | **Oxford portfolio** | `app/lib/oxford-dul-projects.ts` + `app/projects/oxford-dul-2025/` + `app/components/oxford-dul/` | -| **Nav** | Bold labels; active link = accent + pill underline (`.nav-link` in `globals.css`) | +| **Nav** | Desktop/mobile text links use the shared blue/yellow palette with no decorative underline (`.nav-link` in `globals.css`) | | **List link pattern** | `hover-link-hint.tsx` — leading ↗ on hover, trailing `Label ↗`; row hover bg on list rows | -| **Misc** | Watching / Remembrance / Things I Love (nested `mag-card-inset` categories) | -| **Typography** | Geist on `body`; **Nunito** for nav + `.mag-card` / `.mag-label` | -| **Theme** | `theme-provider.tsx` — default **light** when unset | +| **Misc** | Things I Love (nested `mag-card-inset` categories) / Resources / Watching / Remembrance | +| **Typography** | Geist Sans / Mono is the base system; **Nunito** is used across the editorial UI (nav, headings, cards, lists, controls) | +| **Theme** | White/black page bases; structural blue `#628ea7`; hover yellow `#f8b364`; layered blue card borders/shadows | +| **Theme mode** | `theme-provider.tsx` — default **light** when unset | --- diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index cc39902..2b4360d 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -71,7 +71,7 @@ reporter of any incident. For this repository, contact the maintainer: -- Via the email on the [About](https://kaichen.dev/about) page, or +- Via [kaichen.dev.37@gmail.com](mailto:kaichen.dev.37@gmail.com) (also available from the homepage Contact links), or - Through GitHub ([@kaiiiichen](https://github.com/kaiiiichen)) for non-sensitive matters. For **security vulnerabilities**, do not use a public issue — follow [SECURITY.md](SECURITY.md) instead. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2fec594..a6e415d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,7 +17,7 @@ Before you contribute: | Human contributors | This file + [README](README.md) | | AI coding agents (Cursor, Claude Code, etc.) | [AGENTS.md](AGENTS.md) — **branch + PR workflow** and commit conventions are mandatory | -Maintainers use **pull requests to `main`**; direct pushes to `main` are discouraged even when permissions allow. +All changes go through **pull requests to `main`**; do not push directly to `main`, even when permissions allow. --- @@ -94,11 +94,13 @@ npm run build - **Next.js 16** App Router, **React 19**, **TypeScript**, **Tailwind CSS 4**, **Vitest 4** for unit tests. - `dev` and `build` use Next.js' default **Turbopack** bundler (no MDX in this repo). -- **Fonts:** Geist Sans/Mono on `` / default `body`; **Nunito** for nav and magazine-style cards (see [`app/globals.css`](app/globals.css), [`app/layout.tsx`](app/layout.tsx)). +- **Fonts:** Geist Sans/Mono provide the base type system; **Nunito** is used across the editorial UI (nav, headings, cards, lists, and controls). See [`app/globals.css`](app/globals.css), [`app/layout.tsx`](app/layout.tsx), and [`app/fonts.ts`](app/fonts.ts). ### Style - Match existing component patterns, naming, and Tailwind usage. +- Preserve the shared interaction palette: structural/link blue (`--link-color`), yellow hover/focus (`--link-hover-color`), and the layered blue `.mag-card` border/shadow hierarchy. +- Keep `PageToc` discovery intact when restructuring pages: prefer explicit `TocSection` targets or existing `.mag-label` sections, and exclude fullscreen routes such as `/music-for-life/map`. - Prefer **small, reviewable diffs** over large refactors unless discussed in an issue first. - **Dark mode:** new UI should work in light and dark (`dark:` variants and existing theme patterns). diff --git a/README.md b/README.md index 6ebb2f8..c589ccf 100644 --- a/README.md +++ b/README.md @@ -17,15 +17,15 @@ Next.js, React, TypeScript, Tailwind, Node.js, Vitest, Vercel

- + - kaichen.dev + kaichen.dev Deployed on Vercel - License GPL-3.0 + License GPL-3.0 Dependabot @@ -89,11 +89,11 @@ To do that, the site mixes several kinds of content: | Kind | Purpose | Examples on this site | | ----------------- | ---------------------------------- | ------------------------------------------------------------------------------------------ | | **Narrative** | Background and intent that change slowly | About; project write-ups | -| **Live signals** | Small windows into the present | Spotify now playing; Berkeley weather and local clock; GitHub pins and contribution calendar | +| **Live signals** | Small windows into the present | Spotify now playing; Berkeley weather and local clock; live GitHub project metadata | | **Curated lists** | Taste and attention, hand-picked | Misc | -Visually, the site uses an **editorial / magazine** layout: cream-toned cards (`.mag-card`), Nunito for nav and card chrome, Geist Sans for body text, light mode by default with full dark-mode support. The goal is reading comfort and personality — a hub you can revisit, not a feature dashboard. +Visually, the site uses an **editorial / magazine** layout: white cards with layered blue borders and shadows, yellow hover accents, Nunito for editorial UI, and Geist Sans / Mono as the base type system. Light mode is the default, with full dark-mode support. The goal is reading comfort and personality — a hub you can revisit, not a feature dashboard. This repository implements that idea with **Next.js 16** (App Router), **React 19**, **TypeScript**, **Tailwind CSS 4**, deployed on **Vercel**. Dynamic data comes from **Spotify**, **GitHub GraphQL**, **Open-Meteo**, and optional **Supabase** listening history. Optional observability: **Sentry**, **Vercel Analytics**, **Speed Insights**. @@ -104,11 +104,11 @@ There is **no** `middleware.ts` / `proxy.ts` — all routes are public and rende | Route / link | Role | | ------------ | -------------------------------------------------------------------- | -| `/` | First impression — identity, social links, Listening, Location, Projects; **PageToc** (Kai T. Chen → Greeting → Contact → cards) | +| `/` | First impression — identity, social links, Listening, Location; **PageToc** (Kai T. Chen → Greeting → Contact → cards) | | `/about` | Deeper self — education, Focus, experience, volunteering | | `/projects` | Course Projects, Personal Projects | | `/music-for-life` | **Music for Life** — curated public Spotify playlists; index cards link to `/music-for-life/[id]` for tracks; place journals link cities to `/music-for-life/map` | -| `/misc` | Watching, Remembrance, Things I Love | +| `/misc` | Things I Love, Resources, Watching, Remembrance | **Open Graph:** each in-app route has an `opengraph-image` handler (`[app/lib/og.tsx](app/lib/og.tsx)`). **SEO:** `sitemap.ts`, `robots.ts`, and JSON-LD `Person` on the home page. @@ -116,13 +116,16 @@ There is **no** `middleware.ts` / `proxy.ts` — all routes are public and rende ### Visual tokens -| Token | Hex | Use | -| ---------------- | --------- | ---------------------------------------------- | -| Cream background | `#F7F3EE` | Page atmosphere (`--background`) | -| Accent | `#C4894F` | Links, hovers, editorial emphasis (`--accent`) | -| Dark accent | `#D9A870` | Accent in `.dark` mode | +| Token | Hex | Use | +| ---------------------- | --------- | ---------------------------------------------------------- | +| Light background | `#FFFFFF` | Page and card base (`--background`) | +| Dark background | `#000000` | Page base in `.dark` | +| Link / structural blue | `#628EA7` | Links, primary card borders, active TOC (`--link-color`) | +| Hover yellow | `#F8B364` | Link, card, chip, social, and album-art hover emphasis | +| Soft blue border | `#B7CBD6` | Secondary borders; dark variant `#456577` | +| Pale blue shadow | `#DFE8ED` | Card depth; dark variant `#243640` | -**Hover tips:** home social icons and card help bubbles use `[HoverTip](app/components/hover-tip.tsx)` — **70ms show delay**, fade + slide (`globals.css` `.hover-tip-bubble`, 220ms ease). Place-name links on playlist pages use row-style hover (background first, accent text via `group-hover`, 300ms). +**Interaction language:** links start blue and turn yellow on hover/focus; magazine cards lift with yellow border emphasis; project/list rows use a soft blue surface. Home social icons and card help bubbles use `[HoverTip](app/components/hover-tip.tsx)` — **70ms show delay**, fade + slide (`globals.css` `.hover-tip-bubble`, 220ms ease). Place-name links on playlist pages use the same row-hover pattern. **Page TOC:** fixed right rail on long pages (`[page-toc.tsx](app/components/page-toc.tsx)`) — hover/focus expands a jump list. Sections from `[TocSection](app/components/toc-section.tsx)` (`data-toc-item`) and `.mag-label` headings. Hidden on `/music-for-life/map` and when the viewport gutter is too narrow. Legacy **`/playlists`** URLs **301** redirect to **`/music-for-life`** (`next.config.ts`). @@ -185,7 +188,7 @@ npm run lint && npm run typecheck && npm run test && npm run build | ---------- | ------------------------------------------------------------------------------------------------------ | | Framework | Next.js **16** (App Router, **Turbopack**), React **19**, TypeScript **5** | | Styling | Tailwind CSS **4**, magazine cards in `[app/globals.css](app/globals.css)` (`.mag-card`, `.mag-label`) | -| Fonts | **Geist Sans / Mono** on `body` (`geist/font`); **Nunito** via `next/font/google` (`app/fonts.ts`) for nav + cards; star counts use Geist Mono | +| Fonts | **Geist Sans / Mono** as the base type system (`geist/font`); **Nunito** via `next/font/google` (`app/fonts.ts`) across nav, headings, cards, lists, and controls; star counts use Geist Mono | | Data | Optional **Supabase** (`listening_history` / `listening_stats`) for Spotify route | | Monitoring | Optional **Sentry**; **Vercel Analytics** + **Speed Insights** | | Testing | **Vitest** — see `[lib/*.test.ts](lib/)` | @@ -199,14 +202,14 @@ Pinned versions: `[package.json](package.json)`. kaichen.dev/ ├── app/ │ ├── layout.tsx # Root layout, metadata, theme, Nav, SiteFooter, Analytics -│ ├── page.tsx # Home (identity, Spotify, weather, nested Projects) +│ ├── page.tsx # Home (identity, social links, Listening, Location) │ ├── globals.css # Theme tokens, .mag-card, .mag-card-inset, .nav-link, animations │ ├── opengraph-image.tsx # OG for / │ ├── about/ # Bio, education, focus, experience + OG │ ├── projects/ -│ │ ├── page.tsx # Course + Personal project cards +│ │ ├── page.tsx # Course + Personal project lists │ │ └── oxford-dul-2025/ # Oxford summer coursework portfolio (landing + [slug]) -│ ├── misc/ # Watching, Remembrance, Things I Love + OG +│ ├── misc/ # Things I Love, Resources, Watching, Remembrance + OG │ ├── music-for-life/ # Music for Life — index, [id] detail, /map viewer + OG │ │ ├── page.tsx # Curated playlist cards (Spotify metadata) │ │ ├── [id]/page.tsx # Full track list per playlist @@ -217,8 +220,8 @@ kaichen.dev/ │ │ ├── github/contributions/ │ │ └── weather/ │ ├── components/ -│ │ ├── oxford-dul/ # Portfolio cards, project sections, projects-split -│ │ ├── nav.tsx # Bold labels; pill-shaped active underline +│ │ ├── oxford-dul/ # Portfolio cards, project sections, learning path +│ │ ├── nav.tsx # Desktop/mobile nav using the shared blue/yellow link palette │ │ └── … # listening-card, hover-tip, page-toc, toc-section, playlist-description, playlist-map, … │ ├── fonts.ts # Nunito via next/font/google │ ├── hooks/use-now-playing.ts @@ -251,18 +254,18 @@ kaichen.dev/ | Route | What it does | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `/` | Identity; social links; **Listening** + **Location** cards; **Projects** outer card; **PageToc** (Kai T. Chen, Greeting, Contact, Listening, Location, Projects) | +| `/` | Identity; Email, GitHub, LinkedIn, and X social links; **Listening** + server-rendered **Location** cards; **PageToc** (Kai T. Chen, Greeting, Contact, Listening, Location) | | `/about` | Personality, education (incl. Oxford → portfolio link), Focus, experience, volunteering | -| `/projects` | **Course Projects** cards; **Personal Projects** grid; GitHub contribution calendar | +| `/projects` | Separate **Course Projects** and **Personal Projects** list cards; descriptions, topics, stars, and archive state merge from GitHub with local fallbacks | | `/projects/oxford-dul-2025` | Oxford **Deep Unsupervised Learning** landing — overview, learning path, 6 project cards, academic record | | `/projects/oxford-dul-2025/{slug}` | Per-project detail — Problem, Dataset, Approach, Training, My Work, Evaluation, Results, Takeaway | | `/music-for-life` | **Music for Life** — curated playlist IDs in `[app/lib/spotify-playlists.ts](app/lib/spotify-playlists.ts)` (display order = array order). Descriptions live from Spotify; place journals split **period** / **places** on two lines; city names link to Mapbox via coords in config. Index → `/music-for-life/[id]` for tracks. `revalidate = 3600`. Home Listening chip: **music for life** | | `/music-for-life/[id]` | Single playlist — cover, Spotify description, two-column track list, back chip to `/music-for-life`; PageToc = playlist title + **Tracks** (not per-song) | | `/music-for-life/map` | Fullscreen Mapbox GL viewer for a place (`?lng=&lat=&zoom=&label=`); compact footer, no page scroll | -| `/misc` | **Watching** (dated news links), **Remembrance**, **Things I Love** (nested category inset cards) | +| `/misc` | **Things I Love** (nested category inset cards), **Resources**, **Watching** (dated news links), and **Remembrance** | -**Nav:** bold **Kai T. Chen** + **About · Projects · Misc**; active route uses accent text + **pill-shaped underline** (`app/globals.css` `.nav-link`). Site footer on every page: `© 2026 Kai T. Chen. All rights reserved.` +**Nav:** **Kai T. Chen** + **About · Projects · Misc** use the shared blue/yellow text palette with no decorative underline (`app/globals.css` `.nav-link`); mobile navigation closes on route changes and site-logo navigation. Site footer on every page: `© 2026 Kai T. Chen. All rights reserved.` #### API routes @@ -360,7 +363,7 @@ vercel --prod # after vercel link | Area | Where to edit | | ------------------- | --------------------------------------------------------------------------------------------------------- | -| Copy, social URLs | `app/page.tsx`, `app/about/page.tsx` | +| Copy, social URLs | `app/page.tsx`, `app/about/page.tsx`, `[app/components/home-social-links.tsx](app/components/home-social-links.tsx)` | | Course Projects | `[app/lib/course-projects.ts](app/lib/course-projects.ts)` — Oxford entry + external links (e.g. Notion) | | Oxford portfolio | `[app/lib/oxford-dul-projects.ts](app/lib/oxford-dul-projects.ts)`, `app/projects/oxford-dul-2025/`, `app/components/oxford-dul/` | | Portfolio PNG assets | `public/portfolio/oxford-dul-2025/{slug}/` — see `[portfolio/oxford-dul-2025/README.md](portfolio/oxford-dul-2025/README.md)` | @@ -368,7 +371,7 @@ vercel --prod # after vercel link | Spotify playlists | `[app/lib/spotify-playlists.ts](app/lib/spotify-playlists.ts)` — `CURATED_SPOTIFY_PLAYLISTS` (order = display order; `placeCoords` for Mapbox links) | | GitHub login / projects | `GITHUB_LOGIN`, `[app/lib/personal-projects.ts](app/lib/personal-projects.ts)`, contributions route | | Supabase schema | `[lib/listening-supabase.ts](lib/listening-supabase.ts)`, now-playing route | -| Weather location | `[app/api/weather/route.ts](app/api/weather/route.ts)` | +| Weather location | `[app/lib/weather.ts](app/lib/weather.ts)` | | Misc lists | `[app/misc/page.tsx](app/misc/page.tsx)` | | Theme / fonts / nav | `app/layout.tsx`, `app/globals.css`, `app/components/nav.tsx`, `theme-provider.tsx` | @@ -383,10 +386,14 @@ Keep **GPL-3.0** compliance if you redistribute — see `[LICENSE](LICENSE)`. | **README.md** | This file — idea + reference | | `[portfolio/oxford-dul-2025/README.md](portfolio/oxford-dul-2025/README.md)` | Oxford portfolio assets, routes, export checklist | | `[CONTRIBUTING.md](CONTRIBUTING.md)` | Human contributor guide | +| `[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)` | Contributor Covenant and enforcement contacts | | `[AGENTS.md](AGENTS.md)` | AI agent git workflow | | `[CLAUDE.md](CLAUDE.md)` | Short stack summary | | `[SECURITY.md](SECURITY.md)` | Responsible disclosure | +| `[.github/ISSUE_TEMPLATE/](.github/ISSUE_TEMPLATE/)` | Bug/feature forms and private security link | +| `[.github/pull_request_template.md](.github/pull_request_template.md)` | PR scope, validation, and documentation checklist | | `[.env.example](.env.example)` | Env var names | +| `[LICENSE](LICENSE)` | GNU GPL v3 terms | #### License @@ -439,11 +446,11 @@ Keep **GPL-3.0** compliance if you redistribute — see `[LICENSE](LICENSE)`. | 类型 | 作用 | 在本站的具体体现 | | -------- | ------------ | -------------------------------- | | **叙述** | 变化较慢的背景与方向 | About;项目说明 | -| **实时信号** | 照进「此刻」的小窗口 | Spotify 正在播放;Berkeley 天气与时钟;GitHub 项目 star 与贡献日历 | +| **实时信号** | 照进「此刻」的小窗口 | Spotify 正在播放;Berkeley 天气与时钟;GitHub 项目实时元数据 | | **策展列表** | 品味与注意力,人工挑选 | Misc | -视觉上采用**杂志 / editorial** 排版:奶油色卡片(`.mag-card`)、Nunito 用于导航与卡片 chrome、Geist Sans 用于正文、默认浅色并完整支持暗色。目标是阅读舒适与个性 —— 可回访的**枢纽**,而非功能控制台。 +视觉上采用**杂志 / editorial** 排版:白色卡片配分层蓝色边框与阴影,hover 使用黄色强调;Nunito 覆盖 editorial UI,Geist Sans / Mono 作为基础字体系统。默认浅色,并完整支持暗色。目标是阅读舒适与个性 —— 可回访的**枢纽**,而非功能控制台。 技术实现:**Next.js 16**(App Router)、**React 19**、**TypeScript**、**Tailwind CSS 4**,部署于 **Vercel**。动态数据来自 **Spotify**、**GitHub GraphQL**、**Open-Meteo**、可选 **Supabase** 听歌记录。可选观测:**Sentry**、**Vercel Analytics**、**Speed Insights**。 @@ -454,11 +461,11 @@ Keep **GPL-3.0** compliance if you redistribute — see `[LICENSE](LICENSE)`. | 路由 / 链接 | 作用 | | --------- | ----------------------------------------------- | -| `/` | 第一印象 —— 身份、社交链接、Listening、Location、Projects;**PageToc**(Kai T. Chen → Greeting → Contact → 卡片) | +| `/` | 第一印象 —— 身份、社交链接、Listening、Location;**PageToc**(Kai T. Chen → Greeting → Contact → 卡片) | | `/about` | 更深的自我 —— 教育、Focus、经历、志愿 | | `/projects` | Course Projects、Personal Projects | | `/music-for-life` | **Music for Life** — 精选公开 Spotify 歌单;索引卡片链到 `/music-for-life/[id]` 看曲目;地点日记的城市名链到 `/music-for-life/map` | -| `/misc` | Watching、Remembrance、Things I Love | +| `/misc` | Things I Love、Resources、Watching、Remembrance | **Open Graph:** 各站内路由均有 `opengraph-image`(`[app/lib/og.tsx](app/lib/og.tsx)`)。**SEO:** `sitemap.ts`、`robots.ts`,首页嵌入 JSON-LD `Person`。 @@ -468,13 +475,16 @@ Keep **GPL-3.0** compliance if you redistribute — see `[LICENSE](LICENSE)`. ### 视觉 token -| 序号 | 色值 | 用途 | -| --- | --------- | -------------------- | -| 01 | `#F7F3EE` | 页面背景(`--background`) | -| 02 | `#C4894F` | 强调色(`--accent`) | -| 03 | `#D9A870` | 暗色模式强调色 | +| Token | 色值 | 用途 | +| ----------- | --------- | ----------------------------------------- | +| 浅色背景 | `#FFFFFF` | 页面与卡片基础色(`--background`) | +| 暗色背景 | `#000000` | `.dark` 页面基础色 | +| 链接 / 结构蓝 | `#628EA7` | 链接、主卡片边框、当前 TOC(`--link-color`) | +| Hover 黄 | `#F8B364` | 链接、卡片、chip、社交图标与专辑封面的 hover 强调 | +| 柔和蓝边框 | `#B7CBD6` | 次级边框;暗色变体 `#456577` | +| 淡蓝阴影 | `#DFE8ED` | 卡片层次;暗色变体 `#243640` | -**Hover 气泡:** 首页社交图标与卡片帮助提示使用 `[HoverTip](app/components/hover-tip.tsx)` — **70ms 延迟**后淡入 + 上滑(`globals.css` `.hover-tip-bubble`)。歌单页地名链接采用行级 hover(先背景、再 accent 文字,300ms)。 +**交互语言:** 链接默认蓝色,hover / focus 转为黄色;杂志卡片上浮并以黄色强调边框;项目与列表行使用柔和蓝色表面。首页社交图标与卡片帮助提示使用 `[HoverTip](app/components/hover-tip.tsx)` — **70ms 延迟**后淡入 + 上滑(`globals.css` `.hover-tip-bubble`)。歌单页地名链接沿用同一行级 hover 模式。 **Page TOC:** 长页右侧固定目录(`[page-toc.tsx](app/components/page-toc.tsx)`),悬停/聚焦展开跳转列表;区块来自 `[TocSection](app/components/toc-section.tsx)` 与 `.mag-label`。`/music-for-life/map` 不显示。旧 **`/playlists`** 路径 **301** 重定向至 **`/music-for-life`**。 @@ -539,7 +549,7 @@ npm run lint && npm run typecheck && npm run test && npm run build | --- | --------------------------------------------------------------------------------------- | | 框架 | Next.js **16**(App Router、**Turbopack**)、React **19**、TypeScript **5** | | 样式 | Tailwind CSS **4**,杂志卡片见 `[app/globals.css](app/globals.css)`(`.mag-card`、`.mag-label`) | -| 字体 | `body` 用 **Geist Sans / Mono**;导航与卡片用 **Nunito**;代码处用 JetBrains Mono | +| 字体 | **Geist Sans / Mono** 作为基础字体系统;导航、标题、卡片、列表与控件广泛使用 **Nunito** | | 数据 | 可选 **Supabase**(`listening_history` / `listening_stats`),供 Spotify 路由使用 | | 监控 | 可选 **Sentry**;**Vercel Analytics** + **Speed Insights** | | 测试 | **Vitest** —— 见 `[lib/*.test.ts](lib/)` | @@ -553,14 +563,14 @@ npm run lint && npm run typecheck && npm run test && npm run build kaichen.dev/ ├── app/ │ ├── layout.tsx # 根布局、metadata、主题、Nav、SiteFooter、Analytics -│ ├── page.tsx # 首页(身份、Spotify、天气、嵌套 Projects) +│ ├── page.tsx # 首页(身份、社交链接、Listening、Location) │ ├── globals.css # 主题 token、.mag-card、.mag-card-inset、.nav-link、动画 │ ├── opengraph-image.tsx # 首页 OG │ ├── about/ # 简介、教育、Focus、经历 + OG │ ├── projects/ -│ │ ├── page.tsx # Course + Personal 项目卡片、GitHub 热力图 +│ │ ├── page.tsx # Course + Personal 项目列表 │ │ └── oxford-dul-2025/ # 牛津暑校作品集(落地页 + [slug] 详情) -│ ├── misc/ # Watching、Remembrance、Things I Love + OG +│ ├── misc/ # Things I Love、Resources、Watching、Remembrance + OG │ ├── music-for-life/ # Music for Life — 索引、[id] 详情、/map 地图 + OG │ │ ├── page.tsx │ │ ├── [id]/page.tsx @@ -571,8 +581,8 @@ kaichen.dev/ │ │ ├── github/contributions/ │ │ └── weather/ │ ├── components/ -│ │ ├── oxford-dul/ # 作品集卡片、详情区块、projects-split -│ │ ├── nav.tsx # 粗体标签;圆角 active 下划线 +│ │ ├── oxford-dul/ # 作品集卡片、详情区块、learning path +│ │ ├── nav.tsx # 使用蓝黄链接色的桌面 / 移动导航 │ │ └── … # listening-card、hover-tip、page-toc、toc-section、playlist-description、playlist-map 等 │ ├── fonts.ts # Nunito(next/font/google) │ ├── hooks/use-now-playing.ts @@ -602,18 +612,18 @@ kaichen.dev/ | 路由 | 功能 | | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `/` | 身份;社交链接;**Listening** / **Location**;嵌套 **Projects**(Course + Personal 列表行,hover ↗) | +| `/` | 身份;Email、GitHub、LinkedIn、X 社交链接;**Listening** / 服务端渲染的 **Location**;PageToc = Kai T. Chen、Greeting、Contact、Listening、Location | | `/about` | 性格、教育(含牛津作品集链接)、Focus、经历、志愿 | -| `/projects` | Course Projects 卡片;Personal Projects 网格;GitHub 贡献日历 | +| `/projects` | 独立的 Course Projects 与 Personal Projects 列表卡片;说明、topics、star 与 archived 状态从 GitHub 合并,并保留本地 fallback | | `/projects/oxford-dul-2025` | 牛津 **Deep Unsupervised Learning** 落地页 | | `/projects/oxford-dul-2025/{slug}` | 单项目详情(Problem / Dataset / Training / Evaluation / Results 等) | | `/music-for-life` | **Music for Life** — 配置在 `[app/lib/spotify-playlists.ts](app/lib/spotify-playlists.ts)`(数组顺序即展示顺序)。简介来自 Spotify;地点日记 **时间 / 地点** 分两行;城市名通过 `placeCoords` 链 Mapbox。索引 → `/music-for-life/[id]`。`revalidate = 3600`。首页 chip:**music for life** | | `/music-for-life/[id]` | 单张歌单 — 封面、简介、双列曲目、返回 `/music-for-life`;PageToc = 歌单名 + **Tracks**(非逐曲) | | `/music-for-life/map` | 全屏 Mapbox 地图(`?lng=&lat=&zoom=&label=`);紧凑页脚、禁止页面滚动 | -| `/misc` | **Watching**、**Remembrance**、嵌套 **Things I Love** | +| `/misc` | 嵌套 **Things I Love**、**Resources**、**Watching**、**Remembrance** | -**导航:** 粗体 **Kai T. Chen** + **About · Projects · Misc**;当前页为 accent 文字 + **圆角下划线**(`.nav-link`)。页脚:`© 2026 Kai T. Chen. All rights reserved.` +**导航:** **Kai T. Chen** + **About · Projects · Misc** 使用统一蓝黄文字色,不使用装饰下划线(`.nav-link`);移动菜单在路由或站点 logo 导航后自动关闭。页脚:`© 2026 Kai T. Chen. All rights reserved.` #### API 路由 @@ -713,7 +723,7 @@ vercel --prod # 需先 vercel link | 区域 | 修改位置 | | -------------- | -------------------------------------------------------------------------------------------------------- | -| 文案、社交链接 | `app/page.tsx`、`app/about/page.tsx` | +| 文案、社交链接 | `app/page.tsx`、`app/about/page.tsx`、`[app/components/home-social-links.tsx](app/components/home-social-links.tsx)` | | Course Projects | `[app/lib/course-projects.ts](app/lib/course-projects.ts)` | | 牛津作品集 | `[app/lib/oxford-dul-projects.ts](app/lib/oxford-dul-projects.ts)`、`app/projects/oxford-dul-2025/`、`app/components/oxford-dul/` | | 作品集 PNG 资源 | `public/portfolio/oxford-dul-2025/{slug}/` —— 见 `[portfolio/oxford-dul-2025/README.md](portfolio/oxford-dul-2025/README.md)` | @@ -721,7 +731,7 @@ vercel --prod # 需先 vercel link | Spotify 歌单 | `[app/lib/spotify-playlists.ts](app/lib/spotify-playlists.ts)` — `CURATED_SPOTIFY_PLAYLISTS`(顺序即展示;`placeCoords` 供 Mapbox 链接) | | GitHub 用户 / 项目 | `GITHUB_LOGIN`、`[app/lib/personal-projects.ts](app/lib/personal-projects.ts)`、contributions 路由 | | Supabase 表结构 | `[lib/listening-supabase.ts](lib/listening-supabase.ts)`、now-playing 路由 | -| 天气坐标 | `[app/api/weather/route.ts](app/api/weather/route.ts)` | +| 天气坐标 | `[app/lib/weather.ts](app/lib/weather.ts)` | | Misc 列表 | `[app/misc/page.tsx](app/misc/page.tsx)` | | 主题 / 字体 / 导航 | `app/layout.tsx`、`app/globals.css`、`app/components/nav.tsx`、`theme-provider.tsx` | @@ -736,10 +746,14 @@ vercel --prod # 需先 vercel link | **README.md** | 本文件 —— 理念 + 技术参考 | | `[portfolio/oxford-dul-2025/README.md](portfolio/oxford-dul-2025/README.md)` | 牛津作品集资源、路由、导出清单 | | `[CONTRIBUTING.md](CONTRIBUTING.md)` | 人工贡献指南 | +| `[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)` | Contributor Covenant 与执行联系人 | | `[AGENTS.md](AGENTS.md)` | AI agent Git 流程 | | `[CLAUDE.md](CLAUDE.md)` | Claude Code 简要说明 | | `[SECURITY.md](SECURITY.md)` | 安全披露 | +| `[.github/ISSUE_TEMPLATE/](.github/ISSUE_TEMPLATE/)` | Bug / Feature 表单与私密安全入口 | +| `[.github/pull_request_template.md](.github/pull_request_template.md)` | PR 范围、验证与文档清单 | | `[.env.example](.env.example)` | 环境变量名 | +| `[LICENSE](LICENSE)` | GNU GPL v3 条款 | #### 许可证 @@ -750,4 +764,4 @@ vercel --prod # 需先 vercel link --- -[kaichen.dev](https://kaichen.dev/) \ No newline at end of file +[kaichen.dev](https://kaichen.dev/) diff --git a/SECURITY.md b/SECURITY.md index 9303f3a..9779074 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ We do **not** provide a formal bug bounty program. Reports are handled **best-ef Instead: 1. **Contact the maintainer privately**, using one of: - - The email listed on the [About](https://kaichen.dev/about) page, or + - [kaichen.dev.37@gmail.com](mailto:kaichen.dev.37@gmail.com), or - GitHub **private security advisories** for this repository (if enabled), or - A direct message to [@kaiiiichen](https://github.com/kaiiiichen) for **non-sensitive** coordination only (not for long exploit write-ups). diff --git a/app/about/page.tsx b/app/about/page.tsx index ac8f6ba..c4cac6c 100644 --- a/app/about/page.tsx +++ b/app/about/page.tsx @@ -47,7 +47,7 @@ export default function About() { years: "2026 – 2026", institution: "University of California, Berkeley", role: "Visiting Student", - subtitle: "Computer Science and Data Science", + subtitle: "Computer Science", grade: "4.00 / 4.00", }, { diff --git a/app/components/home-social-links.tsx b/app/components/home-social-links.tsx index ffc590a..e7632c3 100644 --- a/app/components/home-social-links.tsx +++ b/app/components/home-social-links.tsx @@ -3,7 +3,7 @@ import type { ReactNode } from "react"; import HoverTip from "./hover-tip"; -type SocialBrand = "email" | "github" | "x" | "linkedin" | "spotify"; +type SocialBrand = "email" | "github" | "x" | "linkedin"; const SOCIAL_LINKS: Array<{ href: string; @@ -63,18 +63,6 @@ const SOCIAL_LINKS: Array<{ ), }, - { - href: "https://open.spotify.com/user/31xktcnl7hl34lu4windhu5uwji4?si=46cd7158fc2a4ab0", - label: "Spotify", - ariaLabel: "Spotify profile", - brand: "spotify", - external: true, - icon: ( - - - - ), - }, ]; export default function HomeSocialLinks() { diff --git a/app/components/listening-track-row.tsx b/app/components/listening-track-row.tsx index 87ee4f0..910a6d0 100644 --- a/app/components/listening-track-row.tsx +++ b/app/components/listening-track-row.tsx @@ -3,9 +3,9 @@ import { HoverLinkArrow, HoverLinkDestinationHint } from "./hover-link-hint"; const HOVER_RING = "ring-0 group-hover/track:ring-[1.5px] " + - "group-hover/track:ring-black/45 group-hover/track:ring-offset-[2px] " + + "group-hover/track:ring-[var(--link-hover-color)] group-hover/track:ring-offset-[2px] " + "group-hover/track:ring-offset-white " + - "dark:group-hover/track:ring-white/40 dark:group-hover/track:ring-offset-[#1F1F23]"; + "dark:group-hover/track:ring-offset-[#1F1F23]"; type ListeningTrackRowProps = { title: string; diff --git a/app/globals.css b/app/globals.css index 54d7b8c..371a5c3 100644 --- a/app/globals.css +++ b/app/globals.css @@ -503,13 +503,11 @@ button.mag-chip:disabled { .home-social-link--email:hover, .home-social-link--github:hover, .home-social-link--x:hover, -.home-social-link--linkedin:hover, -.home-social-link--spotify:hover { color: var(--link-hover-color); } +.home-social-link--linkedin:hover { color: var(--link-hover-color); } .dark .home-social-link--email:hover, .dark .home-social-link--github:hover, .dark .home-social-link--x:hover, -.dark .home-social-link--linkedin:hover, -.dark .home-social-link--spotify:hover { color: var(--link-hover-color); } +.dark .home-social-link--linkedin:hover { color: var(--link-hover-color); } .home-social-link svg { width: 1.75rem; height: 1.75rem; diff --git a/app/lib/course-projects.ts b/app/lib/course-projects.ts index 58870a1..d5c8100 100644 --- a/app/lib/course-projects.ts +++ b/app/lib/course-projects.ts @@ -17,7 +17,7 @@ export type CourseProjectEntry = { }; /** - * Course projects shown on / and /projects. For GitHub-backed entries the + * Course projects shown on /projects. For GitHub-backed entries the * summary, tags, and stars mirror the repo's About, Topics, and star count — * fetched live; the values below are fallbacks kept in sync by hand. */ diff --git a/app/lib/personal-projects.ts b/app/lib/personal-projects.ts index e625aba..92955e5 100644 --- a/app/lib/personal-projects.ts +++ b/app/lib/personal-projects.ts @@ -12,7 +12,7 @@ export type PersonalProject = { }; /** - * Personal projects shown on / and /projects. Each entry mirrors its GitHub + * Personal projects shown on /projects. Each entry mirrors its GitHub * repo: desc = About, stack = Topics, stars = live count — all fetched live; * the values below are fallbacks kept in sync by hand. */