Project Overview
Last updated: Feb 19, 2026
1. Project Overview
bythewei.dev is Weixiang Zhang’s personal developer portfolio and sprint board, styled as a physical corkboard covered in sticky notes, pushpins, and tape. It functions as a live dashboard showing the current state of the Voxlight product family, alongside personal reading data and book highlight archives.
The site is a single-page application with three major interactive sections:
- Sprint Board — A kanban-style corkboard showing the current development sprint (columns for shipped code, tests, blockers, docs, marketing) with sticky notes driven by
sprint.json. - The Catalog — A hidden bookshelf modal (triggered by a pin icon) that displays all highlighted book passages from Apple Books, rendered as book spines on wooden shelves. Clicking a spine opens the Reading Journal for that book.
- The Reading Year — A data visualization modal showing 150 books read between Feb 2019 and Feb 2020, with genre bars, diversity breakdowns, emotional receipts, and interactive cross-highlighting.
Additionally, the page includes:
- A Quote of the Day drawn deterministically from bookmark highlights (same quote worldwide per calendar day, no server needed).
- A kaomoji rotation engine that swaps all kaomoji faces daily using a date-seeded hash.
- A “Who Is This Guy” section with links to blog, fiction site, Threads, GitHub, Instagram, Goodreads, and Guilty Gear stats.
- A custom 404 page themed as a fallen sticky note.
| Field | Value |
|---|---|
| URL | https://bythewei.dev |
| Hosting | Vercel (static deployment) |
| GitHub | https://github.com/noxwei/bythewei |
| Branch | main |
2. Tech Stack
| Layer | Technology |
|---|---|
| Framework | Astro 5.17+ |
| Adapter | @astrojs/vercel 9.0+ |
| Output mode | static (pre-rendered HTML at build time) |
| TypeScript | Strict (extends astro/tsconfigs/strict) |
| Interactivity | Vanilla JavaScript (inline <script> tags) |
| Styling | Scoped CSS + <style is:global> blocks |
| Fonts | Google Fonts (Permanent Marker, Fredoka, Kalam, Patrick Hand) |
| Analytics | Vercel Web Analytics (built into adapter) |
| Framework islands | None — zero React/Vue/Svelte/Preact |
There are no UI framework dependencies. All interactivity (modals, tooltips, search, cross-highlighting, scroll tracking, localStorage persistence) is written in plain JavaScript inside <script> tags in index.astro.
3. File Structure
bythewei/
|-- package.json # Project manifest, scripts, dependencies
|-- package-lock.json # Lockfile (npm)
|-- astro.config.mjs # Astro config: site URL, Vercel adapter, web analytics
|-- tsconfig.json # TypeScript strict config extending Astro defaults
|-- vercel.json # Vercel deployment config (build command, output dir)
|-- .gitignore # Standard ignores: dist/, node_modules/, .env, .astro/
|-- README.md # Default Astro minimal template readme (not customized)
|
|-- src/
| |-- layouts/
| | |-- Layout.astro # Base HTML layout: <head>, fonts, global CSS, cork texture
| |
| |-- pages/
| | |-- index.astro # THE site (2491 lines): sprint board, catalog, journal,
| | | # reading year, QOTD, kaomoji engine, all CSS + JS
| | |-- 404.astro # Custom 404: fallen sticky note theme
| |
| |-- data/
| |-- sprint.json # Sprint board content: meta, stats, columns, stickies
| |-- kaomojis.ts # 200 kaomojis in categorized array (happy, angry, animals...)
| |-- bookmarks.json # Raw Apple Books highlights export (3204 lines)
| |-- bookmarks.clean.json # Processed highlights: deduped, cleaned, with IDs (17571 lines)
| |-- reading-log.json # 150-book reading year dataset (3751 lines)
|
|-- public/
| |-- favicon.ico # Favicon (ICO format)
| |-- favicon.svg # Favicon (SVG format)
| |-- data/
| |-- bookmarks.clean.json # Public copy of cleaned highlights (fetched at runtime)
| |-- reading-log.json # Public copy of reading log (fetched at runtime)
|
|-- scripts/
| |-- extract-datajar.mjs # Extracts highlights from DataJar JSON/ZIP/.datajar exports
| |-- process-bookmarks.mjs # Full pipeline: clean, normalize, dedupe, generate IDs
| |-- merge-bookmarks.mjs # Incrementally merge new exports into existing clean DB
| |-- strip-location.mjs # Legacy: strip location + clean highlights (superseded by process-bookmarks)
| |-- convert-reading-log.mjs # Converts reading year CSV to JSON for the reading modal
| |-- verify-clean.mjs # QA: checks bookmarks.clean.json for boilerplate/whitespace
|
|-- .vscode/
| |-- extensions.json # Recommends astro-build.astro-vscode extension
| |-- launch.json # Dev server launch config
|
|-- dist/ # Build output (gitignored)
|-- .astro/ # Astro cache (gitignored)
|-- .vercel/ # Vercel local config (gitignored)
4. Getting Started
Prerequisites
- Node.js 18+ (LTS recommended)
- npm
Clone and Install
git clone https://github.com/noxwei/bythewei.git
cd bythewei
npm install
Development Server
npm run dev
Opens at http://localhost:4321. Hot-reloads on file changes.
Build
npm run build
Outputs static files to dist/.
Preview Production Build
npm run preview
Serves the built dist/ directory locally to verify before deploying.
Deploy
Push to main branch. Vercel auto-deploys from GitHub.
git push origin main
5. Configuration
astro.config.mjs
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel';
export default defineConfig({
site: 'https://bythewei.dev',
adapter: vercel({
webAnalytics: { enabled: true },
}),
output: 'static',
});
| Setting | Value | Purpose |
|---|---|---|
site | https://bythewei.dev | Canonical URL for OG tags and sitemap |
adapter | @astrojs/vercel | Vercel deployment adapter |
webAnalytics.enabled | true | Injects Vercel Web Analytics script |
output | static | Pre-renders all pages at build time; no server-side rendering |
tsconfig.json
Extends astro/tsconfigs/strict. Includes .astro/types.d.ts for Astro type support. Excludes dist/.
vercel.json
{
"buildCommand": "npm run build",
"outputDirectory": "dist",
"framework": "astro"
}
6. Build & Deploy
Static Output
The site is fully static. At build time, Astro renders index.astro and 404.astro into HTML files. JSON data files in public/data/ are copied as-is to dist/data/ and fetched by the browser at runtime.
Vercel Deployment
- Trigger: Push to
mainbranch on GitHub. - Build command:
npm run build - Output directory:
dist/ - Framework detection: Astro (configured in
vercel.json) - Analytics: Vercel Web Analytics auto-injected via the adapter.
Environment
No environment variables are required. The site has no server-side logic, no API keys, and no database connections.
7. Content Editing Guide
7a. Update Sprint Board Content
Edit /Users/weixiangzhang/Local_Dev/projects/bythewei/src/data/sprint.json.
The file structure:
{
"meta": {
"date": "Feb 18, 2026",
"title": "VOXLIGHT -- FEB 18 SPRINT",
"subtitle": "One founder. One day. One gate left."
},
"stats": [
{ "value": "111", "label": "tests passing" }
],
"columns": [
{
"header": "SHIPPED CODE",
"stickies": [ ... ]
}
],
"bottom_row": [ ... ],
"footer": "WEIXIANG INC -- BYTHEWEI.DEV -- FEB 18 2026"
}
meta.titleandmeta.subtitleappear in the page banner.statsarray renders as dark stat cards below the banner.columnsarray renders the main kanban grid (currently 5 columns).bottom_rowrenders the freeform stickies at the bottom.footeris the text at the very bottom of the page.
7b. Add New Stickies
Add an object to a column’s stickies array:
{
"color": "g",
"size": "big",
"rotation": "r2",
"tape": true,
"stamp": "DONE",
"title": "Feature Name",
"body": "Description of what shipped.\nLine breaks with \\n.",
"tag": "commit-hash",
"blocker": false
}
| Field | Values | Notes |
|---|---|---|
color | y (yellow), g (green), p (purple), b (blue), o (orange), r (red), w (white), teal | Sets the sticky note gradient |
size | null, "big" | Big stickies get larger padding/font |
rotation | "r1" through "r7" | CSS rotation angle |
tape | true, false, "center", "left", "right" | Tape strip position |
stamp | null, "DONE", "IN PROGRESS", or any string | Green for DONE, orange for anything else |
blocker | true, false | Adds red border + glow |
tag | null or string | Small metadata tag at bottom |
7c. Update Bookmark Highlights (The DataJar Pipeline)
The bookmark data comes from Apple Books highlights, exported via the DataJar iOS app. The pipeline:
-
Export from DataJar — Export a
.datajar,.zip, orstore.jsonfile from DataJar on iOS. -
Extract raw entries:
node scripts/extract-datajar.mjs path/to/export.datajar > src/data/bookmarks.json -
Process into clean format (full reprocess):
node scripts/process-bookmarks.mjsThis reads
src/data/bookmarks.json, cleans highlights (strips boilerplate, normalizes authors, generates SHA-1 IDs, deduplicates), and writes to bothsrc/data/bookmarks.clean.jsonandpublic/data/bookmarks.clean.json. -
Merge new entries (incremental, preserves existing):
node scripts/merge-bookmarks.mjs path/to/new-export.jsonAdds only new entries to the existing clean database without reprocessing everything.
-
Verify output:
node scripts/verify-clean.mjsChecks for remaining boilerplate, whitespace issues, and prints statistics.
The cleaned JSON is fetched at runtime by the browser for the Quote of the Day, the Catalog bookshelf, and the Reading Journal.
7d. Update Reading Log Data
The reading log is a dataset of 150 books read between Feb 2019 and Feb 2020. To update:
-
Export reading data as CSV from the source spreadsheet (expected at
~/Downloads/Reading Data - Primary.csv). -
Convert to JSON:
node scripts/convert-reading-log.mjsOutputs to both
src/data/reading-log.jsonandpublic/data/reading-log.json.
Each book entry includes: title, author, dates, rating (1-5), gender (F/M/N), poc (boolean), emotions array, emotional_output, difficulty, pages, genre, fiction/non-fiction, country, and optional review text.
7e. Add New Kaomojis
Edit /Users/weixiangzhang/Local_Dev/projects/bythewei/src/data/kaomojis.ts.
The file exports a kaomojis string array with 200 entries organized by category (happy, excited, surprised, angry, sad, cool, fighting, love, shrug, animals, magic, tired, running). Add new entries to the appropriate category section. The daily rotation engine uses a date-seeded hash to deterministically select unique kaomojis for each [data-kaomoji] element on the page.
8. Key URLs
| Resource | URL / Path |
|---|---|
| Live site | https://bythewei.dev |
| GitHub repo | https://github.com/noxwei/bythewei |
| Vercel dashboard | https://vercel.com (project: bythewei) |
| Local project | /Users/weixiangzhang/Local_Dev/projects/bythewei/ |
9. Dependencies
The project has exactly two npm dependencies:
| Package | Version | Purpose |
|---|---|---|
astro | ^5.17.1 | Static site generator. Compiles .astro files to HTML, handles asset pipeline, dev server, and build tooling. |
@astrojs/vercel | ^9.0.4 | Vercel deployment adapter. Configures output for Vercel hosting and injects Vercel Web Analytics script. |
There are no other runtime or development dependencies. No CSS frameworks, no bundlers, no testing libraries, no linters configured at the project level. The scripts/ utilities use only Node.js built-in modules (fs, path, crypto, url).
10. Architecture Decisions
Why Astro
Astro is a content-focused static site generator that ships zero JavaScript by default. For a personal portfolio that is essentially one page of styled HTML with some client-side interactivity, Astro provides:
- File-based routing (
src/pages/maps directly to URLs) - Scoped CSS in
.astrofiles (no CSS-in-JS library needed) define:varsfor passing server-side data to inline scripts- Built-in TypeScript support
- First-class Vercel adapter with analytics integration
Why No React/Vue/Svelte
The entire site is a single page with three modals. The interactivity (catalog bookshelf, journal pagination, reading year charts, search, cross-highlighting) is DOM manipulation that would gain nothing from a component framework. Using vanilla JS:
- Ships zero framework runtime (no React, no virtual DOM, no hydration)
- The full page loads as pre-rendered HTML with inline scripts
- Total dependency footprint is 2 packages
- No component lifecycle complexity for what is fundamentally “show/hide divs and build DOM nodes from JSON”
Why Vanilla JS for Interactivity
All interactive features use a single IIFE in a <script> tag. The patterns are straightforward:
fetch()to load JSON frompublic/data/document.createElement()andinnerHTMLfor building catalog spines, journal entries, and chart elementsaddEventListenerfor clicks, hover, keyboard (Escape to close modals)localStoragefor persisting dog-ear bookmarks and scroll positions- CSS classes toggled for highlight states
This is readable, debuggable, and has zero build overhead.
Why Static Output
The site has no server-side logic. All data is baked into JSON files at build time or fetched from public/data/ at runtime. Static output means:
- Instant deploys (just copy files to CDN)
- No cold starts, no serverless functions, no edge runtime
- Free Vercel hosting tier
- The “Quote of the Day” changes daily using a client-side date-seeded hash, requiring no server
Data Architecture
The bookmark and reading data follow a two-copy pattern:
src/data/*.json— imported at build time by Astro for any server-side rendering needspublic/data/*.json— served as static files, fetched by client-side JS at runtime
This allows the QOTD, catalog, and reading year modals to load data lazily after the page renders, keeping the initial HTML payload small while still having the data available for build-time use if needed.
Git Branches
| Branch | Status |
|---|---|
main | Active development, auto-deploys to Vercel |
bookmark-system | Feature branch for bookmark pipeline scripts |
bookmark-integration | Feature branch for catalog/journal UI |
master | Legacy default branch (superseded by main) |
Recent commits (as of Feb 19, 2026):
9977dd2 feat: Reading Year modal + orphan title fixes + catalog saturation
750573d feat: hidden bookshelf -- the memory palace
8bfd37e feat: Zato's String favicon + book highlights popup modal
90bf6c9 feat(bookmarks): full pipeline system -- stable IDs, author normalisation, merge tool
a5dc177 feat: add Vercel Web Analytics