# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. > Inherits from `../CLAUDE.md` — follow parent rules unless overridden here. ## Project Overview Personal portfolio website for Kevin Ho (www.kevinho.com). - **Firebase Project:** `kevinho-cb39f` - **Build Output:** `_site/` (DO NOT edit directly) ## Build Commands ```bash # Build site node build.js # Deploy npx firebase deploy --only hosting --project kevinho-cb39f # Build + Deploy node build.js && npx firebase deploy --only hosting --project kevinho-cb39f # Local preview npx serve _site ``` ## Architecture **Critical:** This project uses a custom Node.js build script (`build.js`), NOT standard Jekyll. The Gemfile exists for reference only. ### How build.js Works 1. Loads `_config.yml` settings and `_data/*.yml` files 2. Initializes LiquidJS engine with `_includes/` as partials and `_layouts/` as layouts 3. Processes all `.html` and `.md` files with front matter 4. Recursively processes `experiments/`, `about/`, and `research/` directories 5. Applies layouts specified in front matter (`layout: default`, `layout: experiment`) 6. Auto-generates `sitemap.xml` from processed pages 7. Copies `assets/` to output ### Directory Structure - `experiments/` — Interactive demos and UI experiments (e.g., health-sim, cognitive-calculator, mice) - `about/` — About pages (CV, etc.) - `research/` — Standalone research write-ups and analysis pages (e.g., sierra, lanternreader) - `styleguide/` — Design system reference (not in sitemap) - `functions/` — Firebase Cloud Functions (Node 20, codebase: "biosim") ## Design System See `/styleguide` for the complete visual reference. Key specs: - **Font:** Inter (weights 300, 400, 500, 600) - **Letter Spacing:** -0.02em globally - **Colors:** #111111 (text), #9ca3af (muted), #ffffff (bg), #22C55E (accent) - **Border Radius:** 4px (images), 12px (cards), 16px (device cards) - **Breakpoint:** md: 768px (mobile-first) ### Template Globals Available in all Liquid templates: - `site` — config values from `_config.yml` plus `site.data` (loaded from `_data/`) - `page` — front matter values plus `page.url` and `page.content` - `content` — rendered page content (in layouts only) ### Front Matter Options ```yaml layout: default|experiment|null # null = raw output, no layout title: Page Title description: SEO description sitemap: false # exclude from sitemap.xml sitemap_priority: 0.9 # default is 0.8 body_class: extra-classes # added to tag hide_nav: true # hides navigation ``` ## Firebase Services Firebase config (`firebase.json`) deploys three services: 1. **Hosting** — Static site from `_site/` 2. **Firestore** — Rules in `firestore.rules` (full lockdown; all access via Admin SDK in Cloud Functions) 3. **Functions** — Source in `functions/`, codebase "biosim" ### Cloud Functions Three HTTP functions in `functions/index.js`: | Function | Purpose | |----------|---------| | `parseBloodwork` | Accepts base64 PDF, extracts biomarkers via Gemini 2.0 Flash (Vertex AI primary, Google AI SDK fallback) | | `createSession` | Creates Firestore session with biomarkers/protocols, generates m.me deep link | | `messengerWebhook` | Meta Messenger webhook — verifies signatures, routes messages to AI agent | **Lib modules:** - `llm-extractor.js` — PDF → structured biomarker JSON (dual LLM fallback: Vertex AI → Google AI SDK) - `ai-agent.js` — Conversational health Q&A via Gemini 2.0 Flash with biomarker context - `messenger-router.js` — State machine: session linking → plan delivery → AI chat - `messenger-api.js` — Meta Graph API v21.0 wrapper (send messages, verify signatures) - `session-manager.js` — Firestore CRUD for sessions (24h TTL) **Secrets** (Firebase params): `GEMINI_API_KEY`, `MESSENGER_PAGE_ACCESS_TOKEN`, `MESSENGER_VERIFY_TOKEN`, `MESSENGER_APP_SECRET`, `MESSENGER_PAGE_ID` ### Deploy Commands ```bash # Deploy functions only npx firebase deploy --only functions --project kevinho-cb39f # Deploy everything (hosting + firestore rules + functions) npx firebase deploy --project kevinho-cb39f ``` ## BioSim (Health-Sim Experiment) The largest experiment — a client-side metabolic simulator at `experiments/health-sim/`. ### Architecture ``` assets/js/biosim/ ├── ui.js — 3-step flow orchestrator (landing → report → simulator) ├── engine.js — Daily simulation loop (365 days) across all modules ├── protocols.js — Biomarker → intervention mapping + protocol store ├── report.js — Biomarker categorization (green/yellow/red) + report card ├── upload.js — PDF/CSV extraction (cloud function + client-side fallback) ├── bioage.js — Levine PhenoAge biological age calculation ├── charts.js — Chart.js multi-canvas registry + rendering ├── constants.js — Drug pharmacokinetics, metabolic equations, thresholds └── modules/ ├── base-module.js — Strategy pattern interface (defineModule) ├── body-composition.js — Weight/fat/lean mass (runs first, writes sharedState) ├── lipid-panel.js — LDL, HDL, triglycerides, ApoB ├── metabolic-panel.js — HbA1c, glucose, insulin, HOMA-IR ├── inflammatory-panel.js — CRP, testosterone, cortisol └── uric-acid.js — Uric acid levels ``` ### Key Patterns - **Strategy pattern** via `defineModule()`: each module implements `initialize`, `step`, `getOutputs`, `getSummary`, `chartConfig` - **Shared state**: body-composition runs first, downstream modules read `sharedState.weightLostKg` - **Baseline comparison**: placebo simulation vs intervention simulation, delta rendering - **URL state encoding**: compressed query params for shareable sim configurations ## Styling Tailwind CSS loaded via CDN in `_includes/head.html`. Custom CSS in `assets/css/site.css` for anything Tailwind can't express. Design tokens in `_config.yml` under `design:` — font family, weights, colors. ## SEO `_includes/seo.html` handles Open Graph, Twitter Cards, canonical URLs, and robots directives. Every page needs `title` and `description` in front matter.