An interactive terminal-style portfolio. The background is a 3D model rendered
in real time as ASCII art (three.js + AsciiEffect), and the foreground is a
fake shell — type help to explore. It includes an interactive world map of
visited countries.
Live: deployed to GitHub Pages via GitHub Actions on every push to main.
| What | Why |
|---|---|
| React 19 | The standard UI library; the app is a single-page app with purely client-side state. |
| Vite | Fast dev server and a zero-config static build — exactly what GitHub Pages needs. |
| TypeScript (strict) | Type safety across the three.js imperative code and React components. |
| Tailwind CSS | All styling is utility classes; no component CSS files to maintain. |
| three.js | Loads the STL model and renders it through AsciiEffect into a live ASCII table. |
This project started as a Deno Fresh (Preact islands) app. It was rewritten as a plain React + Vite SPA because the site has no server-side logic at all — everything happens in the browser — and a static bundle is the simplest thing that can be hosted on GitHub Pages for free.
├── index.html # Single HTML entry point
├── public/ # Static assets copied verbatim to the build
│ ├── favicon.svg
│ ├── models/model.stl # Default 3D model shown on load
│ └── world.svg # World map used by the `world` command
├── src/
│ ├── main.tsx # React bootstrap
│ ├── App.tsx # Composition + terminal command processor
│ ├── index.css # Tailwind entry + global styles
│ ├── components/
│ │ ├── AsciiBackground.tsx # three.js scene, ASCII effect, imperative API
│ │ ├── Terminal.tsx # Fake shell (prompt, output, input)
│ │ └── WorldPanel.tsx # Visited-countries map with pan/zoom
│ └── data/
│ └── visitedCountries.ts # Country lists + continent mapping
└── .github/workflows/deploy.yml # Build & deploy to GitHub Pages
AsciiBackgroundowns the whole three.js world (scene, camera, STL loader, point light,AsciiEffect). React state would be too slow (and irrelevant) for a 60 fps render loop, so the entire scene lives inside a singleuseEffectand never touches React state. The model spins slowly, the light orbits on mobile, and the scene is draggable viaOrbitControls.Appis the only stateful coordinator: it parses terminal commands and updates the terminal output and the world panel visibility.Terminalis purely presentational — it renders the prompt/output and reports submitted commands upward. URLs in output become clickable links.WorldPanelfetchesworld.svg, highlights visited countries by ISO code (with a few selector overrides for countries the SVG names differently), and implements pan/zoom by mutating the SVGviewBox— no map library needed. The wheel listener is attached natively because React registerswheelas passive, which would breakpreventDefault().
Requires Node.js 20+.
npm install
npm run dev # dev server with HMR
npm run build # type-check + production build into dist/
npm run preview # serve the production build locallyDeployment is fully automated by
.github/workflows/deploy.yml: every push to
main builds the site and publishes dist/ with the official
actions/deploy-pages action.
One-time setup: in the repository go to Settings → Pages and set Source to GitHub Actions.
Two details make the build GitHub Pages-friendly:
- Relative base path —
base: "./"invite.config.tsmakes all asset URLs relative, so the same build works athttps://<user>.github.io/<repo>/(a sub-path) as well as at a custom domain root. No hardcoded repository name anywhere. - Runtime asset URLs — files loaded at runtime (
model.stl,world.svg) are resolved throughimport.meta.env.BASE_URLinstead of absolute/paths for the same reason.
| Command | Description |
|---|---|
help |
list all commands |
world |
open the visited-countries map |
socials |
show social links |
clear |
clear terminal output |