Zhang's personal portfolio site was built in collaboration with Agents. It is a hands-on collection where a university developer records and shares his experience with Agents. It grew out of my curiosity about what Agents can do, and each part of the site came from something I learned along the way. The idea started with a simple question: as a developer, how could I not build a personal site? I handed that idea to Agents to process and develop. Throughout the process I owned the overall design, prompt design, review, acceptance, interaction polish, and detail work; the Agents assisted with the tasks and took over all code development. Let's look at the process I used to work with Agents.
Technology Choices
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| UI | React 19 + TypeScript |
| Styling | Tailwind CSS v4 |
| Animation | motion (motion/react) |
| Content | MDX + @next/mdx (mdxRs compiler) |
| Icons | lucide-react |
Content Design: Goal and Positioning
The key is to make the goal and positioning explicit. I told the Agent what the site is for and which functions I needed, which put the ultimate objective into the context. If needed, the Agent could compare each step against that objective and correct course. Harness's goal mode can achieve the same result as this prompt design. Next came the main content design: a record of Agent use across coding, creation, automation, and other scenarios, followed by lessons learned. Finally, a short introduction to myself. The Agent proposed several directions for me to choose from; when I was not satisfied, we talked through adjustments until I made the final call.
Content System Design: An MDX-Driven Data-Flow Engine
The content engine has to support long-term, incremental, multi-format output. This project uses one static site for both roles, and the core question is how to unify them under a single route without sacrificing either experience. All content lives in MDX files under content/. Before the build, generate-content-data.js splits each file into three layers:
| Output | Content | Responsibility |
|---|---|---|
content-data.json | frontmatter metadata | Powers the home page, lists, and featured items |
lib/generated/*.mdx | Chinese body with frontmatter removed | Compiled into body components |
lib/generated/*.en.mdx | English sidecar body with frontmatter removed | Compiled into English body components |
mdx-registry.ts | slug to Chinese/English Component map | Supplies the body component by language and falls back to Chinese when English is missing |
import Practice0 from '@/lib/generated/practice/zhang.log.mdx';
export const practiceComponents: Record<string, ComponentType> = {
'zhang.log': Practice0,
};
The value of the three-layer split is decoupling content from structure: metadata and body compile independently, and each article becomes a component that can be registered or replaced on its own. Adding or removing an article means maintaining one MDX file.
Rendering Strategy
I discussed deterministic static generation with the Agent. Metadata comes from build-time static data, the body comes from compile-time registered components, and routes are pre-generated with generateStaticParams. The site barely makes client-side data requests: the first screen is deterministic, build output is predictable, and deployment has no runtime dependency. This trade of runtime flexibility for build-time determinism fits a personal site whose content does not change often.
Internationalization
Routing carries the language in the path segment under app/[lang], and getDictionary(lang) returns the UI dictionary. Articles and prompts use the same idea: content/{collection}/{slug}.en.mdx is an English sidecar next to the Chinese source, and global-agents.en.md is the English counterpart of the global prompt. The generator reads only the translated metadata fields from the sidecar, keeps shared fields such as date, image, and tags in the base file, and emits separate blogComponentsEn / practiceComponentsEn registries for the English body. getDisplayTag handles tag localization. Language is modeled as a route parameter plus a lookup, without a heavy i18n framework - light enough to stay compatible with static rendering.
Structure Design
The directory structure came from several rounds of discussion and revision with the Agent:
zhang.log/
├── app/ # App Router routes and pages
│ ├── [lang]/ # Language segment (cn / en), statically prerendered per language
│ │ ├── layout.tsx # Root layout, fonts, navigation, footer, and global Provider
│ │ ├── not-found.tsx # Localized 404 for notFound() within a language segment
│ │ ├── [...rest]/page.tsx # Unmatched paths call notFound() and render the segment 404
│ │ ├── page.tsx # Home page data assembly
│ │ ├── HomeClient.tsx # Home visuals, track diagram, and client interactions
│ │ ├── about/ # About page
│ │ │ ├── page.tsx
│ │ │ └── AboutClient.tsx
│ │ ├── agents/page.tsx # Compatibility entry: redirects to the home page #agent-experience
│ │ ├── blogs/ # Blog page routes
│ │ │ ├── page.tsx
│ │ │ ├── BlogClient.tsx
│ │ │ └── [slug]/page.tsx # Article detail (generateStaticParams)
│ │ ├── practice/ # Practice list page routes
│ │ │ ├── page.tsx
│ │ │ ├── PracticeClient.tsx
│ │ │ └── [slug]/page.tsx # Practice detail (generateStaticParams)
│ │ └── openclaw/ # OpenClaw gateway documentation page
│ │ ├── page.tsx
│ │ ├── OpenClawClient.tsx
│ │ └── sections/ # Blueprint, structure, getting started, configuration, experience, scenarios, and maintenance
│ ├── globals.css # Global styles and Tailwind v4 theme colors
│ ├── mdx.d.ts # MDX module type declarations
│ └── icon.png # Site icon
├── components/ # Core shared components
│ ├── AgentExperience.tsx # Agent methods, tools, and experience on the home page
│ ├── AutoHideScrollbar.tsx # Custom scrollbar that appears while scrolling and hides at rest
│ ├── BackToTop.tsx # Back-to-top button
│ ├── CodeBlock.tsx # Language label, copy action, and horizontal scrolling
│ ├── Footer.tsx # Site footer and social links
│ ├── ImageLightbox.tsx # Image lightbox
│ ├── ListFilters.tsx # List search, filter, and sort controls
│ ├── Navbar.tsx # Desktop / mobile navigation and language switch
│ ├── NotFoundView.tsx # Shared 404 content for notFound() fallbacks
│ ├── Pagination.tsx # List pagination
│ ├── ParallaxImage.tsx # Parallax wrapper with progressive image loading
│ ├── ParallaxSection.tsx # Parallax section motion
│ ├── PracticeIcon.tsx # Practice icon mapping
│ ├── PracticeTableOfContents.tsx # Long-read TOC: desktop sidebar + small-screen drawer
│ ├── ProgressiveImage.tsx # Progressive loading for local covers
│ ├── SmoothScroll.tsx # Lenis inertial scrolling
│ ├── useSectionScrollSpy.ts # Active-section scroll highlighting
│ ├── WorkspaceFileMap.tsx # OpenClaw Workspace file relationship map
│ └── ZoomableImage.tsx # Article image lightbox and zoom
├── content/ # Content source (MDX / Markdown), the only content entry point
│ ├── agents/ # Global Agent prompts (Chinese source + English sidecar)
│ ├── blog/ # Blog articles (source + .en.mdx sidecar)
│ ├── openclaw/workspace/ # Six OpenClaw workspace Markdown sources
│ └── practice/ # Practice entries (source + .en.mdx sidecar, driven by constrained frontmatter)
├── docs/ # Project specifications
│ └── mdx-spec.md # MDX authoring specification
├── lib/ # Data and business logic
│ ├── agent-content.ts # Curated Agent tool data for the home page
│ ├── content.ts # Content reading API (imports content-data.json)
│ ├── i18n.ts # Chinese and English copy (cnContent / enContent)
│ ├── i18n-context.tsx # Client-side language context for shared components
│ ├── mdx-registry.ts # Generated: static imports for Chinese and English MDX bodies
│ ├── scenarios.ts # Single source of truth for practice scenario keys
│ ├── tag-display.ts # Tag display language mapping
│ ├── utils.ts # cn() utility (clsx + tailwind-merge)
│ ├── data/content-data.json # Generated: content metadata
│ └── generated/ # Generated: Chinese and English MDX bodies without frontmatter
├── scripts/generate-content-data.js # Generates JSON, the registry, and bodies without frontmatter
├── public/images/ # Avatars, site icons, blog / practice covers, and OpenClaw assets
├── README.md # Project entry point, development workflow, and content conventions
├── DESIGN.md # Visual design-system spec (Google Stitch design.md format)
├── edgeone.json # EdgeOne cache and deployment configuration
├── AGENTS.md / CLAUDE.md # Behaviour rules for the coding Agent
├── proxy.ts # Route middleware (redirects non-language paths to /cn)
├── mdx-components.tsx # MDX rendering component map
├── next.config.ts # Next.js configuration (mdxRs, local images)
├── postcss.config.mjs
├── tsconfig.json / next-env.d.ts
└── package.json / package-lock.json
Style and Interaction Design
I wrote DESIGN.md to unify the visual language. DESIGN.md is an open design-system specification format from Google's Stitch team. It describes a visual identity to coding agents in a durable, structured way. By writing a set of visual rules for the site, it keeps the design language consistent. The site's visual language is:
---
version: alpha
name: ZHANG.LOG
description: A dark editorial personal practice site with a near-black base, oversized paper-white type, fine rules, and restrained Lobster orange status accents.
colors:
ink: "#050505"
surface: "#0b0b0b"
paper: "#f1f1ee"
fog: "#9b9b96"
rule: "#262624"
signal: "#ff5a24"
secondary: "#2dd4bf"
typography:
sans:
fontFamily: Geist
roles: body, headings, controls
mono:
fontFamily: Geist Mono
roles: metadata, indexes, paths, code
display:
fontFamily: Geist
fontSize: clamp(3.2rem, 7.2vw, 6.35rem)
fontWeight: 500
lineHeight: 0.94
letterSpacing: -0.07em
body:
fontSize: 1rem
lineHeight: 1.75
label:
fontSize: 0.625rem
lineHeight: 1.4
letterSpacing: 0.12em
rounded:
panel: 8px
sheet: 16px
stage: 32px
pill: 9999px
spacing:
xs: 4px
sm: 8px
md: 16px
lg: 24px
xl: 32px
layout:
contentMaxWidth: 1120px
mobileGutter: 20px
desktopGutter: 24px
navHeight: 64px
navHeightDesktop: 80px
components:
navbar:
backgroundColor: "rgba(5,5,5,0.88)"
borderColor: "{colors.rule}"
height: "{layout.navHeight}"
button-primary:
backgroundColor: "{colors.paper}"
textColor: "{colors.ink}"
rounded: "{rounded.pill}"
button-secondary:
backgroundColor: transparent
borderColor: "{colors.rule}"
rounded: "{rounded.pill}"
index-row:
borderColor: "{colors.rule}"
backgroundColor: transparent
code-block:
backgroundColor: "{colors.surface}"
textColor: "{colors.paper}"
rounded: "{rounded.panel}"
typography: "{typography.mono}"
---
## Overview
ZHANG.LOG is a continuously maintained personal practice log, not a marketing page built from a portfolio template. Its visual direction is a "dark editorial workbench": a near-black canvas carries oversized paper-white type, fine rules organize information, a monospaced face marks time, numbering, and paths, and a small amount of Lobster orange appears only in status, emphasis, and interaction feedback.
The page should first explain what is being worked on, then show practices, methods, validation, and writing. Hierarchy should come from whitespace, type size, weight, and rules rather than from a large collection of glass cards, gradient headings, or decorative icons. The overall feel should be calm, direct, and auditable, like a carefully edited engineering record.
The home page establishes the site's central metaphor with an "idea → Agent → reality" track diagram; practices and blog posts use index rows instead of a uniform card wall; detail pages organize long-form text, images, and the table of contents into a sustainable reading coordinate system. Motion serves the narrative and feedback rather than replacing information.
## Colors
- **Ink (#050505):** The primary background for pages and navigation, and the inverted text color on paper-white components.
- **Surface (#0b0b0b):** The secondary background for code blocks, media placeholders, and information panels, separated from Ink by only one step.
- **Paper (#f1f1ee):** The color for primary headings, key body text, primary button fills, and the current navigation state. Avoid harsh pure white.
- **Fog (#9b9b96):** Descriptive text, metadata, inactive navigation, and secondary actions.
- **Rule (#262624):** Dividers, outlines, input borders, and low-level containers. Structure comes mainly from rules, not heavy shadows.
- **Signal (#ff5a24):** Errors, the current section, numbering, status dots, and hover emphasis. It is a locator color, not a large-area fill.
- **Secondary (#2dd4bf):** Used for a very small number of status or validation nodes; it must not compete with Signal for priority.
The color priority is fixed as Ink / Paper / Fog / Rule, with Signal occupying only a very small area. Gradients are used only for abstract tracks, image masks, and contextual motion, not for ordinary text cards.
## Typography
- **Body sans:** Geist. Body copy is usually 16px / 1.75, and long-form desktop text may increase to `lg:prose-lg`; descriptive text uses Fog.
- **Heading sans:** Still Geist, using `font-medium` and tight letter spacing to establish hierarchy. The home page display heading uses `clamp(3.2rem, 7.2vw, 6.35rem)`, `line-height: 0.94`, and `letter-spacing: -0.07em`.
- **Mono:** Geist Mono. Used for dates, sequence numbers, paths, code, status, and section numbers; it is not used for long body copy.
- **Label:** 10–12px monospaced uppercase or small Chinese labels with roughly `0.12em`–`0.16em` letter spacing, creating the distinct texture of recorded data.
- **Long-form text:** At most six heading size levels; paragraphs are indented by two characters by default, while list items and table cells are not. Use `<Indent n={N}>` / `<NoIndent>` to override individual paragraphs.
- **Small-screen type:** Adjust type with `clamp()` and the `md:` / `lg:` breakpoints rather than scaling the root font size. Tables and code blocks in article content drop to 12px on small screens.
## Layout
- **Content width:** The main content area is at most `1120px` wide, with 20px side gutters on mobile and 24px from `sm:` up; the long-form body column is narrower again to avoid very wide lines.
- **Navigation:** The fixed top bar is 64px high and becomes 80px from `sm:` up. Desktop links are centered, while the language switch and OpenClaw entry sit on the right; mobile uses a hamburger menu.
- **Home page:** The primary heading and description use an asymmetric 12-column layout; section spacing is mainly `py-16 md:py-24`, and some narrative areas may extend to `py-20 md:py-32`.
- **Lists:** Practices and blogs appear as index rows separated by rules, not as a uniform rounded-card grid. Practice rows use a 4/8 desktop image-text split; blog rows use a 2/7/2/1 structure for date, title, reading time, and arrow.
- **Detail pages:** OpenClaw and practice detail pages use an 8/4 split at `lg:`: body copy on the left, sticky navigation and related content on the right. List and home covers are cropped to 16:9, while the detail hero uses `fit="contain"` to show the complete source image. New source images should preferably use 16:9; `zhang.log.png` is an existing compatibility exception.
- **Breakpoints:** Small-screen base values come first, with `md = 768px` and `lg = 1024px`. Mobile acceptance has a hard minimum of `360×800` at DPR 2.
- **Spacing:** A 4px base scale; common values are `gap-4 md:gap-6`, `p-4 md:p-6`, and `pt-28 sm:pt-36 lg:pt-40`.
## Components
- **navbar:** A full-width, near-black translucent bar with a single Rule border at the bottom; it hides when scrolling down and returns when scrolling up. The current item is marked with Paper and a small Signal dot, without a large floating pill.
- **button-primary:** A solid Paper pill with Ink text, used for high-priority entries such as "View practices"; it brightens on hover and does not use an orange fill.
- **button-secondary:** A transparent pill outlined in Rule for secondary entries; hover only raises the prominence of the border and text.
- **index-row:** The primary list unit for practices, blog posts, and archives. Items are separated by `border-t border-rule`; hover moves the title, arrow, and local background toward Signal, while touch devices use `:active` for immediate press feedback.
- **article-prose:** Shared by practice and blog detail pages. H2 has a section number and bottom divider; H3 has a Signal left rule; the maximum readable width narrows with the detail-page column.
- **longform-nav:** The desktop view shows a complete sticky table of contents; on small screens, the right edge shows a tick entry that opens a bottom drawer. The TOC is portaled to `body` so parallax containers cannot change its fixed-position reference.
- **code-block:** Geist Mono, a Surface background, a language label, and a copy button; long lines scroll horizontally rather than being manually wrapped. Copy remains visible on touch devices.
- **media:** Prefer local assets; covers use progressive blur loading, and article images support a lightbox and zoom. Alt text must describe the content rather than repeat a filename.
- **not-found:** Unknown paths within a language segment and missing content slugs share the same title, explanation, and Home / Practice / Blog entries, using the existing Navbar, Footer, and dark design tokens.
## Motion
- Scrolling inertia is provided by Lenis; section jumps must interrupt inertia immediately and retain revisit-able hash anchors.
- The home page track diagram expresses "idea → Agent → reality"; the bottom CTA can reveal the workbench scenarios on pointer hover or keyboard focus.
- Hover uses only small displacement, border brightening, or color transitions; touch devices provide feedback through `:active` / `data-pressed`.
- Every decorative effect must support `prefers-reduced-motion: reduce`. Animation must not cover body copy, block clicks, or change layout dimensions.
## Quality Bar
- At `360×800` and DPR 2, page `scrollWidth` must be `≤ 360`; wide tables and long code scroll horizontally only inside their own containers.
- Desktop and mobile must both verify navigation, list filtering, pagination, detail navigation, TOC anchors, image lightbox, and back-to-top.
- Internal links must target existing routes, language segments, and anchors; external links must not be empty, must point to valid targets, and must state whether they open a new window.
- Every interactive element needs a visible focus state, an accessible label, and keyboard operation; external links that open a new window must use a safe `rel`.
- Before release, run content generation, type checking, and a production build, and check both Chinese and English routes for 404s or incorrect fallbacks.
## Do's and Don'ts
**Do**
- Use rules, whitespace, and type size to establish structure before considering a container or shadow.
- Use Signal to mark the current state, numbering, status dots, and a small number of key actions.
- Prefer real content density in the layout; lists, tables, and code should be directly scannable.
- Keep small-screen base dimensions, then restore desktop scale with `md:` / `lg:`.
- After every UI change, review desktop and small screens together, covering at least `360×800`.
**Don't**
- Do not spread Signal across large backgrounds or place several high-saturation colors side by side.
- Do not restore the retired Playfair Display, Inter, and JetBrains Mono typeface combination.
- Do not wrap every section in a glass card; the editorial page is led by rules and asymmetric layouts.
- Do not add orange glow, arrows, or hover lift to display-only panels and create a false impression of interactivity.
- Do not scale the root font size for small-screen adaptation, and do not allow horizontal overflow in the page body.
With the design rules in place, the site then went through a pass on small screens (below 768px): spacing, padding, and radii take their base value from mobile and restore the desktop scale at the md: breakpoint; tables and code blocks drop to 12px, with wide tables and long code scrolling horizontally only inside their own containers; article paragraphs are indented by two characters by default and can override that per paragraph. The long-read table of contents collapses into a tick entry on the right of small screens and opens a bottom drawer, and because the TOC nodes are portaled onto body, the parallax containers' transform cannot disturb their fixed positioning.
Once the design language was consistent, I asked the Agent to use the web access and screenshot tools from playwright MCP after each part of the implementation. It could look at the screenshots, compare the result with the intended design, and correct the code. The Agent could also reference screenshots of good web work to adjust the site's presentation and components in real time. Through this repeated loop of prompting, design, review, and acceptance between the human and the AI, the site's presentation was developed.
Other Workflows
Beyond design, structure, and interaction polish, a few workflows ran through the whole project and protected the quality of the collaboration.
Using GitNexus
The Agent uses GitNexus to understand the codebase.
The size of this project makes "just change it" risky. GitNexus adds a layer of context between understanding and editing. It indexes the codebase into a knowledge graph, then exposes call relationships, dependency chains, and execution flows through CLI and MCP.
- Build the index:
node .gitnexus/run.cjs analyzemakes the project structure, symbols, and execution flows queryable. - Understand code: use
queryto find relevant execution flows andcontextto inspect a symbol's callers and callees instead of searching unfamiliar code blindly. - Assess impact: before changing a function, run
impactto see upstream dependencies, direct callers, and the risk range. Stop and confirm when the risk is HIGH or CRITICAL. - Check changes: after editing, use
detect_changesto verify that the impact matches expectations before final acceptance. Running it before a git commit is especially valuable.
These actions are written into the project's AGENTS.md / CLAUDE.md as a required habit: run impact before editing a symbol, run detect_changes around commits, and use query instead of grep when exploring.
Skill Invocation: Manuals for Tools
This project uses the GitNexus skill family: exploring, impact-analysis, debugging, refactoring, guide, and cli. These skills describe how to use GitNexus effectively. When a task matches a skill trigger, the Agent loads the skill before acting instead of rebuilding the context every time.
Summary
In this site build, I mainly managed and monitored the work while the Agent executed the tasks directly. The Agent showed strong programming and design ability, while I contributed overall coordination, prompt design, and acceptance testing. In short, Agents are shifting the human role in programming from coder to manager. People only need to step in for coordination, task definition, key direction decisions, and final acceptance; the rest can be handled by AI Agents with hands and feet.
Project repository: Y-King666/zhang.log