|
1 | | -# personal-portfolio |
| 1 | +# Adam Gasiorek — Portfolio |
| 2 | + |
| 3 | +An interactive terminal-style portfolio. The background is a 3D model rendered |
| 4 | +in real time as ASCII art (three.js + `AsciiEffect`), and the foreground is a |
| 5 | +fake shell — type `help` to explore. It includes an interactive world map of |
| 6 | +visited countries. |
| 7 | + |
| 8 | +**Live:** deployed to GitHub Pages via GitHub Actions on every push to `main`. |
| 9 | + |
| 10 | +## Tech stack |
| 11 | + |
| 12 | +| What | Why | |
| 13 | +| --- | --- | |
| 14 | +| [React 19](https://react.dev) | The standard UI library; the app is a single-page app with purely client-side state. | |
| 15 | +| [Vite](https://vite.dev) | Fast dev server and a zero-config static build — exactly what GitHub Pages needs. | |
| 16 | +| [TypeScript](https://www.typescriptlang.org) (strict) | Type safety across the three.js imperative code and React components. | |
| 17 | +| [Tailwind CSS](https://tailwindcss.com) | All styling is utility classes; no component CSS files to maintain. | |
| 18 | +| [three.js](https://threejs.org) | Loads the STL model and renders it through `AsciiEffect` into a live ASCII table. | |
| 19 | + |
| 20 | +> This project started as a Deno Fresh (Preact islands) app. It was rewritten |
| 21 | +> as a plain React + Vite SPA because the site has no server-side logic at |
| 22 | +> all — everything happens in the browser — and a static bundle is the |
| 23 | +> simplest thing that can be hosted on GitHub Pages for free. |
| 24 | +
|
| 25 | +## Project structure |
| 26 | + |
| 27 | +``` |
| 28 | +├── index.html # Single HTML entry point |
| 29 | +├── public/ # Static assets copied verbatim to the build |
| 30 | +│ ├── favicon.svg |
| 31 | +│ ├── models/model.stl # Default 3D model shown on load |
| 32 | +│ └── world.svg # World map used by the `world` command |
| 33 | +├── src/ |
| 34 | +│ ├── main.tsx # React bootstrap |
| 35 | +│ ├── App.tsx # Composition + terminal command processor |
| 36 | +│ ├── index.css # Tailwind entry + global styles |
| 37 | +│ ├── components/ |
| 38 | +│ │ ├── AsciiBackground.tsx # three.js scene, ASCII effect, imperative API |
| 39 | +│ │ ├── Terminal.tsx # Fake shell (prompt, output, input) |
| 40 | +│ │ └── WorldPanel.tsx # Visited-countries map with pan/zoom |
| 41 | +│ └── data/ |
| 42 | +│ └── visitedCountries.ts # Country lists + continent mapping |
| 43 | +└── .github/workflows/deploy.yml # Build & deploy to GitHub Pages |
| 44 | +``` |
| 45 | + |
| 46 | +### How it works |
| 47 | + |
| 48 | +- **`AsciiBackground`** owns the whole three.js world (scene, camera, STL |
| 49 | + loader, point light, `AsciiEffect`). React state would be too slow (and |
| 50 | + irrelevant) for a 60 fps render loop, so the entire scene lives inside a |
| 51 | + single `useEffect` and never touches React state. The model spins slowly, |
| 52 | + the light orbits on mobile, and the scene is draggable via `OrbitControls`. |
| 53 | +- **`App`** is the only stateful coordinator: it parses terminal commands and |
| 54 | + updates the terminal output and the world panel visibility. |
| 55 | +- **`Terminal`** is purely presentational — it renders the prompt/output and |
| 56 | + reports submitted commands upward. URLs in output become clickable links. |
| 57 | +- **`WorldPanel`** fetches `world.svg`, highlights visited countries by ISO |
| 58 | + code (with a few selector overrides for countries the SVG names |
| 59 | + differently), and implements pan/zoom by mutating the SVG `viewBox` — no map |
| 60 | + library needed. The wheel listener is attached natively because React |
| 61 | + registers `wheel` as passive, which would break `preventDefault()`. |
| 62 | + |
| 63 | +## Development |
| 64 | + |
| 65 | +Requires Node.js 20+. |
| 66 | + |
| 67 | +```bash |
| 68 | +npm install |
| 69 | +npm run dev # dev server with HMR |
| 70 | +npm run build # type-check + production build into dist/ |
| 71 | +npm run preview # serve the production build locally |
| 72 | +``` |
| 73 | + |
| 74 | +## Deployment (GitHub Pages) |
| 75 | + |
| 76 | +Deployment is fully automated by |
| 77 | +[`.github/workflows/deploy.yml`](.github/workflows/deploy.yml): every push to |
| 78 | +`main` builds the site and publishes `dist/` with the official |
| 79 | +`actions/deploy-pages` action. |
| 80 | + |
| 81 | +One-time setup: in the repository go to **Settings → Pages** and set |
| 82 | +**Source** to **GitHub Actions**. |
| 83 | + |
| 84 | +Two details make the build GitHub Pages-friendly: |
| 85 | + |
| 86 | +1. **Relative base path** — `base: "./"` in [`vite.config.ts`](vite.config.ts) |
| 87 | + makes all asset URLs relative, so the same build works at |
| 88 | + `https://<user>.github.io/<repo>/` (a sub-path) as well as at a custom |
| 89 | + domain root. No hardcoded repository name anywhere. |
| 90 | +2. **Runtime asset URLs** — files loaded at runtime (`model.stl`, |
| 91 | + `world.svg`) are resolved through `import.meta.env.BASE_URL` instead of |
| 92 | + absolute `/` paths for the same reason. |
| 93 | + |
| 94 | +## Terminal commands |
| 95 | + |
| 96 | +| Command | Description | |
| 97 | +| --- | --- | |
| 98 | +| `help` | list all commands | |
| 99 | +| `world` | open the visited-countries map | |
| 100 | +| `socials` | show social links | |
| 101 | +| `clear` | clear terminal output | |
0 commit comments