An interactive portfolio built as a desktop OS experience with 3D capabilities. No frameworks — just TypeScript, Three.js, and a custom virtual DOM engine.
Live: vitalii-ponomarov.com
| Layer | Technology |
|---|---|
| Language | TypeScript 5.2 (strict mode) |
| Build | Vite 5 |
| 3D Graphics | Three.js 0.160 |
| Testing | Vitest 1 + jsdom |
| Linting | ESLint 8 + @typescript-eslint |
| Git Hooks | Husky 9 |
| Deployment | AWS S3 + CloudFront |
| CI/CD | GitHub Actions |
No React, Vue, or any UI framework. The component system, virtual DOM, and state management are all custom-built.
- Desktop OS interface — draggable/resizable windows, taskbar, desktop shortcuts, right-click context menu
- 7 animated backgrounds — particles, constellation nodes, glass bubbles, grid distortion, noise gradient, floating shapes, Three.js WebGL
- Interactive 3D world — full Three.js scene accessible as a desktop window
- Portfolio windows — Stack, Experience, Education, Contacts, CV, GitHub, Email
- Client-side routing — history API with deep link support
- localStorage persistence — desktop shortcut positions survive refresh
- SEO optimized — JSON-LD structured data, Open Graph, Twitter Cards, sitemap, robots.txt
- Analytics — Google Tag Manager + Hotjar
src/
├── lib/ # Custom framework
│ ├── Component.ts # Abstract base component (lifecycle: mount/update/unmount)
│ ├── Dom.ts # VNode creation utilities
│ ├── Reconciler.ts # Virtual DOM diffing & patching
│ ├── Store.ts # Pub/sub state store
│ ├── FormValidator.ts # Form validation helpers
│ ├── Types.ts # Core type definitions (VNode, VNodeChild)
│ └── __tests__/
├── apps/
│ ├── os/ # Main portfolio OS app
│ │ ├── components/
│ │ │ ├── Background/ # 7 animated background implementations
│ │ │ ├── ContextMenu/ # Right-click menu
│ │ │ ├── Desktop/ # Root desktop container
│ │ │ ├── DesktopIcon/ # Draggable shortcut icons
│ │ │ ├── Taskbar/ # Bottom bar with window buttons
│ │ │ ├── ThreeWorld/ # Three.js 3D scene (excluded from coverage)
│ │ │ └── WindowFrame/ # Movable/resizable window chrome
│ │ ├── data/ # JSON content (projects, stack, education, etc.)
│ │ ├── types/ # Domain type definitions
│ │ ├── styles/
│ │ ├── __tests__/
│ │ ├── AppRegistry.ts # Maps app IDs to content renderers
│ │ ├── OsState.ts # Global state initialization
│ │ ├── OsTypes.ts # WindowConfig, ShortcutConfig, OsState
│ │ ├── Router.ts # Client-side router
│ │ ├── WindowManager.ts # Window open/close/minimize/maximize/focus
│ │ ├── MetadataManager.ts # Page title & meta tag updates
│ │ └── RouteConfig.ts # Route → app ID mapping
│ └── CounterApp.ts # Minimal example app
├── components/
│ └── CookieConsent.ts
├── styles/
│ ├── theme.css # CSS custom properties / design tokens
│ └── responsive.css
└── main.ts # Entry point
- Node.js 20+
- npm
npm installnpm run devOpens at http://localhost:5173 (or the LAN IP printed in the terminal due to --host).
npm run build
npm run preview # preview the dist/ output locally| Script | What it does |
|---|---|
npm run dev |
Start Vite dev server with --host |
npm run build |
Production build → dist/ |
npm run preview |
Serve the production build locally |
npm run test |
Run all unit tests |
npm run test:coverage |
Run tests with V8 coverage report |
npm run type-check |
tsc --noEmit type validation |
npm run lint |
ESLint over src/ |
npm run check:all |
type-check + lint + test (full quality gate) |
check:all is the gate. It runs automatically on every git commit (via Husky pre-commit hook) and is the required verification step after any code change.
Tests use Vitest with a jsdom environment.
npm run test # run once
npm run test:coverage # with coverage reportCoverage thresholds (enforced by vitest config):
| Metric | Threshold |
|---|---|
| Lines | 80% |
| Functions | 80% |
| Branches | 80% |
| Statements | 80% |
Test locations:
src/lib/__tests__/— Component, FormValidator, Reconcilersrc/apps/os/__tests__/— AppRegistry, MetadataManager, RouteConfig, Router, WindowManagersrc/apps/os/components/WindowFrame/__tests__/— WindowFrame
Excluded from coverage: ThreeWorld, CounterApp, test files, .d.ts files.
Built from scratch — no React, no Vue.
Component— Abstract base class. Subclasses implementrender(): VNode. The base handles mounting to DOM, reconciliation on state changes, and unmounting.Dom— Factory helpers for creatingVNodeobjects (similar toReact.createElement).Reconciler— Diffs old and new VNode trees and applies minimal DOM patches.Store— Pub/sub store. Components subscribe to slices of state; mutations trigger re-renders.
OsState.ts initializes the global Store<OsState> instance. State shape:
interface OsState {
windows: WindowConfig[];
shortcuts: ShortcutConfig[];
activeWindowId: string | null;
currentBackground: string;
}Desktop shortcut positions are persisted to localStorage and rehydrated on load. Conflicting icon positions are resolved automatically.
Router.ts wraps the History API. RouteConfig.ts maps URL paths to app IDs. When a window opens/closes the URL updates automatically; navigating directly to a URL re-opens the corresponding window.
WindowManager.ts owns the full window lifecycle: open, close, minimize, maximize, restore, focus (z-index), drag (move), and resize. Each window is a WindowConfig object stored in OsState.
Deployments trigger automatically on push to the production branch.
Pipeline (.github/workflows/production.yml):
- Checkout + Node 20 setup
npm install && npm run build- OIDC-based AWS role assumption
- Sync
dist/to S3 (--deletefor clean deploy) - CloudFront cache invalidation (
/*)
Required GitHub secrets: AWS_ROLE_ARN, S3_BUCKET_NAME, CLOUDFRONT_DIST_ID
| Hook | Command |
|---|---|
pre-commit |
npm run check:all |
pre-push |
npm run build |
Commits are blocked if type-check, lint, or any test fails.