A single-page portfolio site built with vanilla HTML, CSS, and JavaScript. No framework, no build step — open index.html and it runs.
Splash screen — A timed intro card that auto-dismisses after 3 seconds, with a skip button. Body scroll is locked until the fade completes, and the element is removed from layout on transitionend rather than left sitting invisible over the page.
Typewriter about section — The "About me" block renders as a syntax-highlighted JavaScript object that types itself out character by character. The implementation clones the DOM structure first, then replays it node by node, so highlighting spans survive the animation and a caret element tracks the current line as a sibling rather than being injected inside it.
Client-side routing — Four sections (About, Resume, Portfolio, Contact) swap via data-page / data-nav-link attribute matching. No page reloads, no router library.
Portfolio filtering — Projects carry a data-category attribute and filter by Software Development, Data Science, or Finance. Desktop gets a button row; mobile gets a custom dropdown, both driving the same filterFunc.
Project modal with slider — Clicking a project opens a modal populated from data-* attributes on the list item (data-desc, data-demo, data-github, data-images). The image slider supports arrow buttons, clickable dots, left/right keyboard arrows, Escape to close, and touch swipe with a 40px threshold. Navigation controls hide automatically when a project has only one image.
Contact form — Posts to Formspree via fetch with a JSON Accept header, so submission happens without navigating away. The submit button stays disabled until checkValidity() passes, and a hidden _gotcha honeypot field catches naive spam bots.
.
├── index.html
└── assets/
├── css/style.css
├── js/script.js
└── images/
The uploaded files are flat, but index.html references ./assets/css/style.css, ./assets/js/script.js, and ./assets/images/. Place them accordingly or the page will load unstyled.
style.css opens with a design-token block rather than scattering literals through the file:
| Token group | Examples |
|---|---|
| Neutrals | --smoky-black, --eerie-black-1/2, --onyx, --jet |
| Accent | --orange-yellow-crayola, --vegas-gold |
| Type scale | --fs-1 (24px) through --fs-8 (11px) |
| Weights | --fw-300 through --fw-600 |
| Elevation | --shadow-1 through --shadow-5 |
| Motion | --transition-1 (0.25s), --transition-2 (0.5s) |
Dark near-black surfaces with an amber accent, Poppins throughout. Breakpoints at 450px, 580px, 768px, 900px, and 1200px; the sidebar collapses behind a "Show Contacts" toggle on small screens.
All loaded from CDN — nothing to install.
| Library | Purpose |
|---|---|
| Ionicons 5.5.2 | UI icons |
| Prism.js 1.29.0 | Syntax highlighting theme (prism-tomorrow) |
| Google Fonts — Poppins | Typography |
| Formspree | Contact form backend |
git clone <repo-url>
cd <repo>
python3 -m http.server 8000Then open http://localhost:8000. Opening index.html directly via file:// mostly works, but a local server avoids CORS issues with the CDN module scripts.
To deploy, push to a repo and enable GitHub Pages — there is nothing to build.