Software Engineering
PortfolioOS
Personal portfolio engineered as a platform with a custom design system, experiment sandbox, and Crucite content pipeline
Personal portfolio website built with an OS-inspired modular architecture. Astro static site with React islands for interactive experiments, tactical HUD design system, Crucite CMS integration, and dev content tooling. Hosts project case studies, interactive experiments (story engine, performance dashboard), and a derived skills matrix.
- My role
- Sole Developer
- Period
- Jan 2026 – Present
- Platforms
- Web
- Status
- active
Tools & technologies
Impact & Results
- Comprehensive project catalog with structured sections supporting All/Technical/Non-Technical view modes, table of contents with scroll-spy, and filterable index with status/platform/tech filters
- Tier-based skill proficiency matrix across multiple categories with Crucite visibility control, derived automatically from project and experiment metadata
- Interactive experiments sandboxed with error boundaries and performance-tiered hydration, demonstrating capabilities beyond static portfolio presentation
- Full accessibility audit: prefers-reduced-motion support, ARIA labels on all canvas elements, focus-visible rings, skip-to-content link, semantic HTML
- Production-ready deployment via Cloudflare Pages with auto-deploy from main, auto-generated OG images, JSON-LD structured data, sitemap, and comprehensive security headers
- Dev tooling productivity: inline content editing, scaffolding generators, Crucite sync with staleness monitoring, and validation dashboards eliminate context-switching between IDE and CMS
- Regression suite caught real bugs immediately - Its first CI run surfaced two live layout bugs (the experiment header's AI badge and load indicator clipped off-screen on mobile, and the footer overflowing on pages with long paths), both fixed same day
Overview
PortfolioOS is a personal portfolio website engineered as a platform rather than a static page. It hosts project case studies, interactive experiments (a branching story engine, performance dashboards), and a derived skills matrix computed from project tech stacks at build time. The site uses an OS-inspired tactical HUD design system with brass, teal, and orange accents that matches the Crucite aesthetic.
Built with Astro for static rendering and React islands for interactive components, the site integrates directly with Crucite through a content sync pipeline that pulls published project data, career information, and skill contexts via REST API. This means portfolio content is authored once in Crucite and automatically reflected on the public site. The project is deployed on Cloudflare Pages.
Role Summary
- Sole developer, designer, and content architect for the entire platform. Designed the tactical HUD visual language from scratch, including the brass/teal/orange palette, HUD bracket system, clip-path shapes, and typography hierarchy. Built the full Astro/React architecture including content collections, experiment sandboxing, island hydration system, and command palette integration.
- Developed interactive experiments including the Reactive Story Engine and Performance Dashboard. Built the Crucite content sync pipeline with digest-based staleness detection, skill visibility filtering, career profile sync, and tag/domain metadata propagation. Created the dev admin panel with inline content editing, Zod schema validation, scaffolding generators, and career data management, all architected as a Vite plugin excluded from production builds.
- Implemented the build-time OG image generation pipeline, security header configuration with CSP, and centralized shared modules (config.ts, constants.ts, format.ts) that eliminated scattered hardcoded values across the codebase.
Non-Technical Summary
PortfolioOS is a personal portfolio website that presents professional work through a distinctive, carefully crafted visual style inspired by operating system interfaces and tactical displays. Rather than using a generic template, it builds a unique identity through a custom design language: dark surfaces, brass and teal accents, corner bracket ornaments, and monospace typography that feels technical and precise without being difficult to navigate.
The site showcases project case studies spanning XR development, AI personas, and technical training alongside interactive experiments that visitors can try directly in the browser. It is designed to serve two audiences: hiring managers who need a quick, professional overview of capabilities, and technically curious visitors who want to explore deeper into the interactive experiments and implementation details.
Behind the scenes, the site connects to a personal content management system (Crucite) that keeps project information, career details, skill visibility, and metadata synchronized. A development toolset (invisible to visitors) provides content editing, validation, and management features directly in the browser during development. The site also auto-generates social preview images for every page and includes a comprehensive security configuration for production deployment.
Highlights
- Designed and built a complete portfolio platform in Astro with React islands, serving project case studies, interactive experiments, and a tier-based skill proficiency matrix derived at build time from Crucite-synced metadata
- Engineered a tactical HUD design system with brass/teal/orange palette, CSS custom property architecture, and a comprehensive design token system enforcing strict color discipline through a single-source-of-truth theme module
- Built an experiment sandboxing system with three hydration tiers (light, medium, heavy), React error boundaries, and performance-aware launch gates that prevent heavy WebGL/audio experiments from auto-loading on mobile
- Integrated Crucite CMS with digest-based staleness detection and skill visibility filtering, enabling live sync of project content, career data, tags, and domain metadata while maintaining full offline-first static generation
- Implemented a build-time OG image pipeline using satori and resvg-js that auto-generates branded Open Graph PNGs for every page, plus a full security header suite with CSP, HSTS, and cache immutability rules for Cloudflare Pages
Quick Highlights
- OS-informed design language - Tactical HUD aesthetic with brass/teal palette, monospace labels, corner brackets, and chamfered clip-path shapes without sacrificing standard web usability
- Experiment sandbox - Interactive React islands with three hydration tiers, error boundaries, and performance-aware mobile gating for heavy WebGL/audio experiments
- Tier-based skill proficiency matrix - Derived at build time from project and experiment stacks, filtered against Crucite-approved metadata with primary/proficient/familiar ranking across multiple categories
- Crucite CMS integration - Digest-based staleness detection with live sync of projects, career profile, tags, skill visibility, and domain metadata
- OG image generation - Build-time pipeline using satori and resvg-js that auto-generates branded 1200x630 Open Graph PNGs for every page
- Security hardening - Full Cloudflare Pages header suite with Content Security Policy, HSTS with preload, X-Frame-Options, Permissions-Policy, and cache immutability for static assets
- Animated canvas backgrounds - Circuit Trace and Constellation Map algorithms with prefers-reduced-motion support and tab-visibility pausing
- Automated regression suite - Vitest unit tests and Playwright layout/interaction tests across five viewport widths, gated in CI on every push and PR
- Accessible custom UI primitives - From-scratch ARIA-listbox Dropdown component, a shared FilterPill, and a StackList mobile popup that collapses long tech-stack lists behind a native <dialog>
Technical Breakdown
Astro Static Shell and Island Architecture
The site is built on Astro with static site generation and a cloudflare() adapter for deployment. Three layout layers compose the page structure: BaseLayout.astro handles meta tags, font preloading, JSON-LD schema, and the ClientRouter for view transitions; ShellLayout.astro wraps content with navigation, footer, and the Ninja Keys command palette; ExperimentLayout.astro adds breadcrumb navigation, metadata headers, fullscreen toggle, and a table of contents sidebar with scroll-spy.
React islands use three hydration tiers defined in experiment frontmatter: light (client:visible, loads when scrolled into view), medium (client:only, loads immediately but skips SSR), and heavy (manual launch button + client:only, requires user interaction to load). The ExperimentWrapper.tsx component dynamically imports experiment modules from a static map and wraps them in ExperimentErrorBoundary.tsx to prevent crashes from breaking the shell.
Content Collections and Zod Schema Validation
Projects and experiments are MDX files under src/content/ with Zod-validated frontmatter defined in content.config.ts. The project schema includes structured section fields: overview, highlights[], quickHighlights[], technicalBreakdown, roleSummary[], nonTechnicalSummary, systemsUsed[], impactResults[], deepDive, plus metadata like versions[], tags[], cvReady, aiBadge, domain, and links. Astro validates all frontmatter at build time. Experiments support a draft flag that hides content from the live site without deleting it.
Derived Skills Aggregation
The getSkills() function in src/lib/skills.ts aggregates stack arrays from every project and experiment at build time, then filters them against skills-meta.json entries synced from Crucite. Only skills present in the meta file appear on the skills page, respecting Crucite visibility settings. Each skill is enriched with tier (primary, proficient, familiar), categories (language, engine, XR, Unity, framework, tool, platform, cloud, AI), context descriptions, and back-references to source projects. The SkillMatrix.tsx React island renders an interactive, filterable visualization with category breakdowns and tier indicators.
Design Token Architecture
All colors are defined in src/lib/theme.ts as a TypeScript object (theme.accent.default, theme.secondary.glow, etc.) with an rgba() helper for alpha variants. The same values are mirrored as CSS custom properties in the :root block of src/styles/global.css, consumed via Tailwind v4's @theme directive. This dual-channel approach allows React canvas code to import theme directly while Astro/CSS components use var(--accent). Design tokens cover surfaces (four tiers), text (three tiers plus dim/faint), accent (four states), secondary (five states), tertiary, and borders.
HUD Visual System
The tactical aesthetic is implemented through CSS utility classes: .hud-bracket adds teal corner brackets via ::before/::after pseudo-elements that expand on hover; .clip-notch applies a polygon clip-path for chamfered bottom-right corners; .clip-button chamfers the top-right corner of primary CTAs; .tac-label styles uppercase, tracked labels in the tertiary color; .panel provides surface-raised cards with subtle borders. All interaction states use the secondary (teal) color, never the accent.
Animated Canvas Backgrounds
HomeBg.tsx randomly selects between two algorithms: Circuit Trace (PCB-inspired paths with connection nodes) and Constellation Map (star-field with proximity-based connections). Both skip on mobile (<768px) and prefers-reduced-motion, pause on tab hidden via visibilitychange, and use canvas.clientWidth for sizing in Astro's client:only mode.
OG Image Generation Pipeline
scripts/generate-og.mjs uses satori to render React-like JSX templates into SVG, then @resvg/resvg-js to rasterize them as 1200x630 PNGs. The script discovers all project and experiment MDX files, extracts frontmatter titles, and generates branded OG images with the site's typography and color palette. Fonts are fetched from Google Fonts on first run and cached locally. The pipeline runs as a pre-build step integrated into npm run build, ensuring every page has a social preview image without manual asset creation.
Security Headers and CSP
A public/_headers file configures Cloudflare Pages with a full security header suite: Content Security Policy restricting script and style sources, HSTS with preload and includeSubDomains, X-Frame-Options DENY, X-Content-Type-Options nosniff, strict Referrer-Policy, and Permissions-Policy disabling camera/microphone/geolocation. Static assets (fonts, images, favicons) receive immutable cache headers with a one-year max-age.
Crucite Content Sync
The scripts/sync.ts pipeline syncs project content, career data (roles, presentations, education, profile), tags, and domain metadata from Crucite via MCP tools. A digest-based staleness monitor (DevPanel.tsx + useSyncStatus.ts) tracks content freshness and highlights out-of-date fields with dashed outlines (gold for synced fields, teal for manual). Skill visibility is controlled by Crucite: only skills exported to skills-meta.json appear on the portfolio, allowing hidden skills to remain in project stacks without leaking to the skills page. The career editor becomes read-only when Crucite is the source of truth, with a sync button to pull latest data into src/data/career.json.
Shared Module Architecture
Site-wide configuration is centralized across dedicated modules: config.ts provides social links, navigation routes, OG image paths, and platform lists; constants.ts holds display label maps and lookup tables previously duplicated across components; format.ts provides string formatting utilities (toUnderscore, slugify); career.ts exposes the Crucite-synced career profile with date formatting helpers. This centralization eliminated scattered hardcoded values and ensures single-source-of-truth consistency.
Dev Admin Panel
A Vite dev middleware plugin (src/lib/dev-admin-plugin.ts) injects the admin panel only in development builds. Components include DevPanel.tsx (main interface with page-aware section navigation), DevToolkit.tsx (edge toggle and overlay host), ContentEditor.tsx (inline MDX editing), CareerEditor.tsx (career data editor), and HealthDashboard.tsx (build/validation status). Scaffolding and validation scripts (scripts/scaffold.ts, scripts/validate.ts) provide CLI-level content tooling.
Automated Testing and CI
Vitest covers scripts/sync.ts's stub-detection and fallback logic plus a content-health smoke test mirroring npm run validate. Playwright runs layout smoke tests for every page across five viewport widths (375/1280/1366/1440/1920), asserting no visible element overflows the viewport while explicitly ignoring visibility:hidden/display:none/opacity:0 elements such as the mobile table-of-contents drawer, which is parked off-canvas via transform and is fully inert to real users despite a naive scroll-width check flagging it. Interaction tests cover the mobile nav, the filter dropdown (mouse and keyboard), and the heavy-experiment launch gate. .github/workflows/test.yml runs validation and both suites on every push and pull request.
Custom Dropdown and Shared Card Components
Dropdown.tsx implements an ARIA listbox pattern with full keyboard support from scratch (no native <select>), replacing a pill-wall filter UI that pushed the first project card below the fold on mobile; the Status filter kept its pill rendering since it only has three options. A shared FilterPill component replaces a duplicated pill button previously defined separately in ProjectCatalog.tsx and SkillMatrix.tsx. Shared .catalog-card and .ai-badge CSS classes replace copy-pasted card and badge markup across Astro templates and React islands, and the dead ProjectCard.astro (superseded by ProjectCatalog.tsx's React rewrite) was removed.
Mobile Stack List Popup
StackList.astro renders the first 8 tech-stack entries inline and collapses any remainder behind a "+N more" trigger that opens a native <dialog> on mobile; desktop renders the full list unchanged. The dialog is centered with !important rules, since Chromium's top-layer styling for showModal() otherwise overrides normal-priority author CSS for position, margin, and transform.
Systems Used
- Astro Content Collections - Type-safe MDX content with Zod schema validation for projects and experiments, enforcing structured frontmatter at build time with draft support
- Island Architecture - Astro static HTML shell with React islands for interactive components using three hydration tiers (light/medium/heavy)
- Derived Skills Aggregation - Build-time skill matrix computed from project and experiment stack arrays with Crucite visibility filtering and tier-based classification
- Experiment Sandboxing - React error boundaries and hydration modes isolating interactive experiments from the portfolio shell with mobile performance gating
- Tactical HUD Design System - CSS custom properties with brass/teal/orange palette, HUD bracket ornaments, clip-notch shapes, and dual-channel theme distribution (TypeScript + CSS vars)
- Crucite Content Sync - Digest-based staleness detection with live sync of projects, career data, skill visibility, tags, and domain metadata from Crucite CMS
- OG Image Generation Pipeline - Build-time satori + resvg-js pipeline auto-generating branded 1200x630 Open Graph PNGs for every page
- Security Header Configuration - Cloudflare Pages headers with CSP, HSTS preload, X-Frame-Options, Permissions-Policy, and immutable cache rules for static assets
- Dev Admin Panel - Vite dev middleware providing in-browser content editing, Zod validation dashboard, scaffolding generators, and career data management
- Shared Module Architecture - Centralized config, constants, formatting, and career modules eliminating scattered hardcoded values across the codebase
- Command Palette - Ninja Keys web component with navigational commands organized by section (navigation, projects, experiments, external links, actions)
- Reactive Story Engine - Branching interactive fiction with Tone.js audio synthesis, dynamic theming, soul tracking, and canvas waveform visualization
- Automated Testing - Vitest for unit/logic tests and Playwright for layout and interaction e2e tests, both gated in GitHub Actions CI on every push and PR
Deep Dive
Design Philosophy: OS-Informed Web
The central design decision was positioning the site on an "OS-informed web" spectrum, using standard web patterns (scrolling, clicking, navigation) while borrowing the visual language of operating systems. This means monospace accents for metadata, structured key-value pairs, panel borders, status indicators, and breadcrumb paths that feel like file system navigation, without the usability cost of actually simulating an OS. The result is a site that signals technical depth to engineering audiences while remaining fully navigable for non-technical visitors.
Progressive Disclosure Architecture
Content is layered for two audiences. The surface layer (project cards, skills overview, career timeline) serves recruiters who need a 30-second scan. The middle layer (command palette, HUD bracket ornaments, tactical labels, animated backgrounds) rewards technically curious visitors. The deep layer (interactive experiments with audio synthesis, generative narrative, and performance dashboards) demonstrates real engineering capability beyond portfolio presentation.
Content Collection Design
The Zod schema for projects evolved significantly during development. Initially simple (title, summary, stack), it expanded to include structured section fields (overview, technicalBreakdown, roleSummary, etc.) following the Crucite project template. This allows the same content to render in three modes (All, Technical, Non-Technical) via a view toggle in the table of contents sidebar. The schema also includes versions[] for projects that evolved through multiple architectures, tags[] synced from Crucite, cvReady as a publish gate, and draft for hiding work-in-progress experiments from the live site.
Experiment Sandboxing Decisions
The three-tier hydration system (light/medium/heavy) was designed to balance user experience against resource consumption. Light experiments load lazily when scrolled into view, which works well for simple interactive components. Medium experiments use client:only to skip SSR entirely, necessary for components that access browser APIs immediately. Heavy experiments (Reactive Story Engine, Performance Dashboard) require an explicit launch button because they initialize audio contexts, WebGL canvases, or large data structures that would degrade page load. React error boundaries wrap every experiment so a crash in canvas rendering or audio synthesis never breaks the portfolio shell.
Dual-Channel Theme Distribution
A key architectural challenge was making the same color palette available to both Astro/CSS components (which use CSS custom properties) and React canvas code (which needs JavaScript values). The solution: src/lib/theme.ts defines all colors as a TypeScript object with an rgba() helper, while src/styles/global.css mirrors the same values as CSS custom properties consumed via Tailwind v4's @theme directive. This dual-channel approach avoids runtime DOM queries in canvas code while keeping CSS utility classes working normally. The strict rule (no hex values outside theme.ts and the :root block) prevents color drift.
OG Image Generation Strategy
Rather than manually creating social preview images or using a runtime service, the site generates OG PNGs at build time using satori (which renders React-like JSX to SVG without a browser) and @resvg/resvg-js (which rasterizes SVG to PNG using a native Rust binding). The generator script discovers all content MDX files, extracts titles from frontmatter, and renders branded 1200x630 images using the site's typography and color palette. Fonts are fetched from Google Fonts on first run and cached in scripts/.font-cache/. This approach guarantees every page has a social preview image with zero ongoing maintenance: adding a new project or experiment automatically generates its OG image on the next build.
Crucite Integration Strategy
The sync pipeline was designed for progressive adoption. The site can build and deploy with zero Crucite connectivity; all content exists as local MDX files and JSON. Crucite provides the source-of-truth layer: project content, career data, tags, and domain metadata are synced via scripts/sync.ts using MCP tools. A digest-based staleness monitor in DevPanel.tsx tracks content freshness, showing dashed outlines around stale fields (gold for Crucite-synced, teal for manual). When synced content becomes the source of truth, the career editor switches to read-only mode with a sync button, preventing local edits that would be overwritten.
Skill visibility is a key integration point: Crucite controls which skills are exported to the portfolio via its skill context system. The syncSkillsMeta function in the dev admin plugin fetches visible skills from Crucite and writes them to skills-meta.json. The getSkills() function then filters stack-derived skills against this file, ensuring hidden skills remain in project stacks (for accuracy) without appearing on the skills page.
Shared Module Centralization
A pre-launch audit identified hardcoded values scattered across components: hex colors in inline styles, label strings duplicated in multiple files, social URLs repeated in navigation and footer. The refactor extracted these into dedicated modules: config.ts (social links, routes, platform lists, site version), constants.ts (status labels, AI badge labels, tech priority rankings), format.ts (string formatting utilities), and career.ts (Crucite-synced profile with date helpers). This centralization ensures consistency and makes site-wide changes a single-file edit.
Dev Tooling Architecture
The dev admin panel is implemented as a Vite plugin (dev-admin-plugin.ts) that registers dev middleware, ensuring zero runtime cost in production builds. The panel decomposes into focused components: DevPanel.tsx provides page-aware navigation (the buttons change based on the current route), ContentEditor.tsx handles inline MDX editing with live preview, and HealthDashboard.tsx validates Zod schemas and reports build health. Module-level state ensures the sidebar and overlay persist across SPA navigations without React context.
Reactive Story Engine Deep Dive
The story engine experiment demonstrates several advanced patterns. ExperienceProvider.tsx manages soul state through a React context, tracking invisible choice dimensions that determine narrative endings. AudioEngine.ts uses Tone.js to synthesize ambient pads, procedural melodies, and bass lines that shift based on narrative mood. ThemeEngine.ts dynamically swaps CSS custom properties to change the entire visual tone of the story interface. VizEngine.ts renders real-time canvas waveforms synced to the audio output. Stories are defined as data bundles in story-engine/stories/ with arc/cycle/spiral narrative structures, each containing nodes, choices, soul dimension mappings, and audio parameters.
Performance Considerations
The site prioritizes perceived performance through several techniques: font preloading for Space Grotesk, Inter, and JetBrains Mono via <link rel="preload">; staggered entrance animations (fade-in-up with 80ms delays) that create a sequential reveal without blocking rendering; canvas animations that pause on tab hidden and skip entirely on mobile; and static site generation that eliminates server-side rendering latency. The Cloudflare Pages deployment provides edge caching and zero-config CDN distribution, with immutable cache headers on fonts and images for optimal repeat-visit performance.