An editorial "systems notebook" personal site: warm paper ground, cobalt signal accent, oversized asymmetric typography, and a restrained scroll-driven motion grammar. Built with React 19, Vite, Tailwind CSS v4, and Motion for React.
All facts live in src/content/site.ts — never inside layout components.
Edit identity, metrics, experiences, writing, channels, capabilities, and social
links there. Two conventions matter:
requiresVerificationon metrics — unverified values are gated by theshowUnverifiedMetricsflag so nothing is silently published.enabledon channels and social links — disabled entries are filtered out of rendering and leave no empty layout gaps. Never invent URLs or QR codes.primaryEmail/emailConfirmed— the email is a placeholder (see theTODOcomment). WhileemailConfirmedisfalsethe contact CTA points to the portfolio instead of amailto:. SetemailConfirmed = trueonce final.
Substack, Medium, WeChat Official Account, and WeChat Channels are pre-defined in
the channels array with enabled: false. To publish one:
- Fill in
accountName,url,latestItem, and — for WeChat — a verifiedqrImagepath (place the image underpublic/). - Set
enabled: true. - If it has a public profile URL, also flip the matching entry in
socialLinks.
The card and footer link appear automatically; no layout changes needed.
A site-wide toggle (Auto / Reduced / Full) lives in the nav and persists to
localStorage. Auto follows prefers-reduced-motion. Reduced removes
the hero canvas animation, curtain, scroll-driven method pinning, spotlights,
magnetic hover, and swaps transitions for immediate/opacity changes — all content
stays available and in source order. The first-session intro curtain shows once
per browser session (sessionStorage).
- ≥1440px — full composition, five hero words.
- 768–1439px — canvas DPR capped at 1.5.
- <768px — four hero words, cursor previews removed, the Working Method becomes four stacked sections (no pinning), canvas DPR capped at 1.0.
src/content/site.ts— typed content data layer.src/motion/MotionContext.tsx— motion-setting provider + resolvedreduced.src/components/*— one file per section plus sharedprimitives.tsx.src/index.css— fonts, design tokens, global craft.
A second, dark-editorial site shares this design system and lives under routes:
/— personal site (this page)./portfolio— portfolio home (Hero, Featured Work, Project Index, Evidence Archive, Working Method, Capabilities, About & Contact)./portfolio/work/:slug— reusable case-study route. Code-split viaReact.lazyinsrc/routes.tsx, so case-study content stays out of the home bundle and only the active study loads.
All portfolio facts live in src/content/portfolio.ts: projects,
featuredSlugs (the editable flagship set), artifacts, methodStages,
capabilities, portfolioSocial, and caseStudies (keyed by slug). Each
project carries state (Shipped / Prototype / Competition proposal / Research /
Concept), accessState, evidenceStage, outcomeType, and enabled /
requiresVerification flags. Verified outcomes only; otherwise state honest
status. primaryEmail has a TODO and stays a non-mailto CTA until
emailConfirmed is true.
Restricted projects (accessState: "Restricted") never ship confidential data
to the client. The case-study route renders only the public abstract plus a
Request access interface — there is no client-side password check.
Restricted routes emit noindex, nofollow and are kept out of the sitemap. To
enable real restricted review, wire the "Request a review link" action to
server-side authentication or a short-lived signed URL; do not embed secrets,
private images, or full restricted case data in JS, JSON, or HTML.
Each route sets its own document.title / description; the case study also sets
robots. For production SSG/SSR, promote these to route-level metadata.
The Vite dev server runs automatically inside Figma Make. Locally: pnpm dev.
Routing uses react-router-dom (Data mode via createBrowserRouter).