Lab
This site is built like documentation on purpose. It is a place to share what I have learned, step by step, so students can follow the path instead of just looking at the result. This page explains how it works: the idea, the tools, the code and the design.
Why a docs site
Most portfolios are a list of wins. That is useful for recruiters, but not for a student who wants to know how to get there. Documentation has a different job: it explains, in order, so someone else can repeat it.
- Logs read like a changelog of a career: where I studied, worked and what each step taught me.
- Projects read like case studies: where the idea came from, what broke, what it became.
- Every page has the same shape (lead, numbers, sections, “On this page”), so it is easy to skim and easy to come back to.
- The look follows Vercel’s docs: quiet, black and white, with structure doing the work instead of decoration.
The goal is not to look impressive. The goal is that a student finishes a page and thinks: I could do the next step too.
Tech stack
| Layer | Tool | Why |
|---|---|---|
| Framework | Next.js 15.5 (App Router) | File-based routes, static generation, image and font optimization out of the box. |
| UI | React 19 | Server components by default; only interactive parts ship JavaScript. |
| Language | TypeScript 5.9 | All content is typed, so a missing field is a build error, not a broken page. |
| Styling | Plain CSS with design tokens | One globals.css with CSS variables. No Tailwind, no CSS-in-JS: small, readable, easy to theme. |
| Fonts | Geist Sans, Geist Mono, IBM Plex Sans Thai | Loaded with next/font, self-hosted, no layout shift. Thai falls back to Plex automatically. |
| Theme | next-themes 0.4 | Light and dark mode with no flash on load; preference saved per visitor. |
| Booking | Custom picker (+ optional Cal.com embed) | Works with zero accounts today; switches to Cal.com with one environment variable. |
| Images | Python + Pillow | Turns logos and my handwriting into transparent masks that follow the theme. |
| AI | Claude Code in Orca | My pair programmer: wrote the code, checked pages in a browser and deployed. |
| Hosting | Vercel | Every deploy is a static build on a global CDN at natch-soros.vercel.app. |
There is no database, CMS or backend. Every page is generated at build time from TypeScript files, which keeps the site fast, free to host and impossible to break with a bad login.
How the code is organized
app/
page.tsx home
about/page.tsx about me and the dream
logs/ career docs (overview + [slug])
projects/ project docs (overview + [slug])
talk/page.tsx booking page
lab/page.tsx this page
globals.css design tokens and every style
components/
header.tsx nav, mega menu, mobile drawer
sidebar.tsx docs sidebar with Ctrl K filter
toc.tsx "On this page" with scroll spy
docs.tsx renders entries from content blocks
career-path.tsx Education / Venture / Tech tracks
booker.tsx calendar, slots, invite links
logos.tsx black and white logo strips
lib/
site.ts name, email, booking hours
content/
logs.ts projects.ts career.ts orgs.ts lab.tsContent and layout are separate. A log or project is plain data: a title, a lead, some numbers and a list of sections. Each section is made of small blocks.
type Block =
| string // paragraph
| { type: "list"; items: string[] }
| { type: "steps"; items: { title; text }[] } // timeline
| { type: "roles"; items: { title; period; points }[] }
| { type: "callout"; tone?: "note" | "accent"; text }
| { type: "table"; head: string[]; rows: string[][] }
| { type: "quote"; text }
| { type: "code"; text; lang? };To add a project, I add one object to projects.ts. The sidebar, mega menu, overview cards, timeline, prev/next links and the static page are all generated from it.
Design system
Everything is driven by a small set of CSS variables. Dark mode is the same variables with different values.
| Token | Light | Dark | Used for |
|---|---|---|---|
| --bg | #ffffff | #000000 | Page background |
| --fg | #171717 | #ededed | Main text |
| --muted | #666666 | #a1a1a1 | Secondary text |
| --border | #ebebeb | #1f1f1f | Lines, cards, grids |
| --accent | #ff4d00 | #ff5a14 | Current items, dots, calendar |
- Accent color: the orange from my handwritten logo, used only for “now”: current roles, available days, the active section.
- Type: Geist for Latin, tight letter-spacing on headings, mono for dates and labels.
- Layout: a three-column docs grid (260px sidebar, 760px reading column, 220px table of contents) that collapses to one column on phones.
- Lines instead of boxes: timelines, hairline borders and grid cells carry the structure, like Vercel’s docs.
- Handwritten logo → theme-aware mark
The logo is a hand-drawn PNG. A short Python (Pillow) script turns the ink into an alpha mask. CSS paints it with currentColor through mask-image, so the same file is black in light mode and white in dark mode.
- Partner logos → one monochrome wall
Every organization logo goes through the same pipeline: remove the background, keep only the shape as a mask, then render it in the theme color. Nearly thirty logos in different colors become one calm grid.
- The glow
The hero is two radial gradients and a CSS drop-shadow on the masked logo, a nod to the glowing triangle on vercel.com.
Features worth explaining
Booking without a backend
- Weekly hours, closed days and date overrides live in lib/site.ts.
- Slots are calculated in Bangkok time and shown in the visitor’s own time zone with Intl.DateTimeFormat.
- Confirming creates a Google Calendar invite with me as a guest, so the booking reaches my calendar without any server.
- Setting NEXT_PUBLIC_CAL_LINK swaps the picker for a Cal.com embed with real availability and Meet links.
Email that always works
- mailto: does nothing on computers without a mail app, so every email button copies the address (or a ready-made message) and offers “Open mail app” as a second option.
Docs navigation
- Mega menu generated from the content categories.
- Sidebar filter with Ctrl K.
- “On this page” highlights the section you are reading as you scroll.
Career path in three tracks
- Education, Venture and Tech, each oldest to newest, so the growth is readable one thread at a time.