An interactive personal lab by Xiaoran Tang: a motion-led portfolio where AI agents, developer tools, and human context meet.
- Live site: profile.tannerlab.xyz
- GitHub: xiaoran7/Resume
- Previous mirror: git.tannerlab.xyz/xiaorana/personal-website
This is more than a static resume page. It is a small, self-contained product surface with four connected layers:
- A cinematic, chapter-based personal story.
- A case-study archive for experiments in agents, developer tools, and speech-first learning.
- An operating-system-style LUMEN workspace with windows, apps, memory, and a knowledge base.
- An optional server-side bridge to a separate AI agent service, with a local mock provider for offline development.
The design language treats the site as a Human-AI Lab. Xiaoran is the creator, observer, and system designer; LUMEN is a supporting guide; projects are experiments that make the relationship between people and machines tangible.
- Motion-led narrative: the home page moves through Entry, Hero, Human Context, Selected Work, LUMEN, System Blueprint, Capabilities, and Contact chapters.
- Scroll-driven hero: desktop and mobile portrait sequences, mouse parallax, signal paths, progress feedback, and a maple-leaf handoff are orchestrated with GSAP and ScrollTrigger.
- Three case studies: SOLUM, AGENT PLATFORM, and ENGLISH ROOM are defined in
content/projects.tsand rendered as archive cards and full project stages. - LUMEN OS:
/workspaceprovides a responsive desktop metaphor with a dock, shortcuts, draggable windows, minimize/maximize/close states, and apps for LUMEN, Projects, Notes, Terminal, Memory, Contact, Settings, and Trash. - Agent-ready chat:
/api/lumen/chatstreams token and tool events from an optional sidecar over SSE, while the built-in mock provider keeps the site usable without another service. - Consent-based memory: long-term visitor memory is opt-in, visitor-scoped, and manageable through pause and forget-all controls.
- Honest contact delivery: the contact route only sends to a webhook when
CONTACT_WEBHOOK_URLis configured; otherwise it falls back to a visible mail link. - Security-conscious defaults: server-only service tokens, same-origin checks, request limits, lightweight rate limiting, CSP, frame denial, and privacy-preserving observability are part of the implementation.
The following images were captured from the current local build at 1440x900 and 390x844 with a real Chromium browser. They show the actual public UI, not a mockup.
| Selected work archive | LUMEN guidance console |
|---|---|
![]() |
![]() |
| Route | Purpose |
|---|---|
/ |
Motion-led personal lab and chapter narrative |
/projects |
Selected work archive |
/projects/[slug] |
Overview, highlights, stack, milestones, and next-project navigation |
/workspace |
LUMEN OS desktop and applications |
/about |
Personal story and design position |
/resume |
Printable resume document |
/api/lumen/chat |
SSE chat proxy with provider selection and request limits |
/api/lumen/memory |
Visitor-scoped memory controls |
/api/internal/agent/rag/index |
Authenticated knowledge indexing endpoint |
/api/internal/agent/rag/retrieve |
Authenticated knowledge retrieval endpoint |
flowchart LR
Browser["Browser UI<br/>Portfolio + LUMEN OS"] --> Next["Next.js App Router"]
Next --> Chat["/api/lumen/chat<br/>SSE proxy"]
Chat --> Provider{"Provider"}
Provider -->|mock| Mock["Built-in mock provider"]
Provider -->|container| Agent["Optional agent-website sidecar"]
Next --> Memory["Visitor memory<br/>SQLite + WAL"]
Next --> Knowledge["Site knowledge<br/>RAG index/retrieve"]
Agent --> Knowledge
The external agent is intentionally a separate repository and deployment boundary. The website never exposes the service token to the browser. Each request gets a request ID; server observability records route, character counts, timing, and error type without storing message content.
| Setting | Behavior | Use case |
|---|---|---|
LUMEN_PROVIDER=mock |
Uses the local deterministic provider | Default development and public code review |
LUMEN_PROVIDER=container |
Proxies to LUMEN_CONTAINER_URL over the sidecar contract |
A connected deployment with agent-website |
When the remote provider is unavailable before a response begins, the client can fall back to the mock provider. Streaming tool actions are allowlisted to navigation, scrolling, and opening an app.
- Long-term memory is off until a visitor explicitly opts in.
- Anonymous visitors receive a random, HttpOnly, SameSite cookie; production enables
Secure. - Memory is isolated per visitor and stored in a SQLite database under
SITE_DATA_DIRwith WAL enabled. - The Memory app supports pause and Forget all; destructive requests require same-origin validation.
- Chat requests are capped at 8 KB and 2,000 characters, with a lightweight per-visitor rate limit.
- Logs carry request IDs, route names, character counts, and timings, but not message bodies.
next.config.tssets CSP,X-Frame-Options,nosniff, COOP, Referrer Policy, and Permissions Policy headers.
- Next.js 16.3 App Router and React 19
- TypeScript 5.7
- Tailwind CSS 4
- GSAP, ScrollTrigger, and
@gsap/react - Lenis smooth scrolling
- Canvas 2D, HTML video, Web Audio, and IntersectionObserver
- ESLint and TypeScript static analysis
- Node.js 22 and Docker for the standalone production image
- SQLite for visitor memory persistence
- Node.js 22 (the production image is built on
node:22-alpine) - npm 10 or newer
- Optional: Docker and a running
1panel-networkonly for the deployment compose file
git clone https://github.com/xiaoran7/Resume.git
cd Resume
npm ci
cp .env.example .env.local
npm run devOpen http://localhost:3000. The default LUMEN_PROVIDER=mock keeps the project independent from the optional agent sidecar.
| Variable | Default | Description |
|---|---|---|
LUMEN_PROVIDER |
mock |
mock or container |
LUMEN_CONTAINER_URL |
http://agent-website:8100 |
Internal URL of the optional agent sidecar |
LUMEN_SERVICE_TOKEN |
empty | Server-only shared token for the sidecar and internal RAG routes |
SITE_DATA_DIR |
./data |
Directory for the SQLite memory database |
CONTACT_WEBHOOK_URL |
empty | Optional HTTPS endpoint for validated contact form JSON |
npm run lint
npm run typecheck
npm run check
npm run buildnpm run check runs linting, TypeScript validation, and the production build used by Docker.
Personal portrait art, character videos, project covers, and background media are intentionally excluded from Git by .gitignore and .dockerignore. This keeps private or large media out of the public source snapshot and leaves the visual identity replaceable.
To reproduce the full visual presentation locally, provide your own licensed files at the paths used by the components, for example:
public/assets/portrait/hero-frames/{desktop,mobile}/frame-01.webp ... frame-40.webp
public/assets/lumen/mahiru-*.mp4
public/assets/lumen/*-poster.webp
public/assets/projects/<slug>/cover.webp
public/assets/projects/<slug>/showcase.mp4
public/assets/bg/*.mp4
The screenshots in docs/screenshots/ are documentation captures of the current presentation. They do not grant rights to the underlying personal or third-party imagery.
Dockerfile builds a minimal Next.js standalone image on Node 22. docker-compose.yml binds the app to 127.0.0.1:3102, mounts ./assets for late-bound media, persists LUMEN memory in a named volume, and expects an external 1panel-network plus an optional agent-website service. It is therefore a deployment-oriented compose file, not a zero-configuration local stack.
app/ App Router pages and server routes
components/ UI, motion, media, LUMEN, and workspace components
content/ Owner profile, projects, and site knowledge
lib/lumen/ Provider protocol, tools, consent, and server storage
lib/lumen-os/ Window-manager state machine
public/assets/ Runtime visual-media locations
docs/screenshots/ Public README captures
Dockerfile Node 22 standalone production image
docker-compose.yml Loopback deployment with assets and data volumes
The source code is released under the MIT License. The personal name, brand, screenshots, and any excluded or third-party media remain subject to their own rights and are not a blanket grant of trademark or image rights.
Built and maintained by Xiaoran Tang. The project is an ongoing study of the boundary between people and machines: observe, model, make, listen, revise.



