π mirconegri.com
A modern, bilingual, interactive personal portfolio β built entirely with HTML, CSS, and vanilla JavaScript, with no framework, no build step, and no bundler.
The deliberate constraint of zero dependencies forced every interactive feature β the 3D hero, the aurora background, the command palette, the world map, the film grain overlay β to be implemented from first principles. The result is a site that loads in under a second, scores well on Lighthouse, and has no supply-chain risk from third-party packages.
- Preview
- Features
- Tech Stack
- Design Tokens
- Design Decisions
- Project Structure
- Getting Started
- Usage
- Configuration and Environment
- Contributing
- License
| Section | Dark | Light |
|---|---|---|
| Hero | ![]() |
![]() |
| Projects | ![]() |
![]() |
| Education | ![]() |
![]() |
| Volunteering | ![]() |
![]() |
| Places | ![]() |
![]() |
| Contact | ![]() |
![]() |
| Changelog | ![]() |
![]() |
| About | ![]() |
![]() |
| Privacy | ![]() |
![]() |
- Light and dark mode with fluid transition, persisted via
localStorage - Bilingual (Italian / English) toggle without page reload β driven by
data-it/data-enattributes on every text node, with no separate page or route per language - Aurora canvas background β real-time 2D canvas animation with drifting color blobs that adapt to the active theme via composite blend modes
- 3D wireframe icosahedron in the hero section built with Three.js, tracking mouse movement
- Morphing cursor orb β a custom element that follows the pointer and continuously morphs its border-radius and hue
- 3D tilt cards β vanilla JS perspective transforms on project and volunteer cards based on mouse coordinates
- Scroll-reveal animations via Intersection Observers, animated top progress bar, and a navbar that hides on downward scroll
- Film grain overlay β SVG noise texture animated purely in CSS, zero canvas cost
- Intro screen β animated first-visit overlay with a typing effect, shown once per session via
sessionStorage, skippable - Command palette (βK) β fuzzy-searchable navigation and quick actions: jump to section, toggle theme or language, copy email, open project links
- Interactive world map β custom Canvas 2D implementation with Natural Earth-derived borders, hoverable countries, and pinned locations (home, university, trips, scout routes)
- Fully responsive β custom mobile menu, fluid CSS grid, and
clamp()-based typography - Contact form via Formspree with a required privacy-policy consent checkbox
- Privacy policy pages for both the site (
privacy.html) and the companion LifeOS app (lifeos-privacy.html), both bilingual
Work in progress:
js/scrollytelling.jsimplements a Three.js + GSAP ScrollTrigger fly-through sequence with aDEBUG_MODEwaypoint editor. This script is not currently loaded byindex.htmland depends onhouse.glbplus GSAP/ScrollTrigger/GLTFLoader CDN scripts that are not yet wired in. It is an unfinished feature, not active functionality.
- Markup: HTML5 with
data-it/data-enlocalization attributes - Styling: CSS3 β custom properties, grid, flexbox,
clamp(), responsive breakpoints - Scripting: Vanilla JavaScript β Intersection Observers, Canvas 2D, Web Animations API,
sessionStorage,localStorage - 3D: Three.js r128 via CDN
- Icons: Font Awesome 6.5.0 via CDN
- Typography: Self-hosted Inter and JetBrains Mono via
@font-faceβ migrated from Google Fonts to avoid transmitting visitor IP addresses to Google's servers, as noted in the site's own Privacy Policy - Forms: Formspree (contact form backend β no custom server required)
- Hosting: Cloudflare Pages β every push to
maintriggers a deploy in approximately 30 seconds
index.htmlloads the Leaflet CSS stylesheet from CDN, but no Leaflet JS is present and noL.map()calls exist anywhere in the codebase. The world map is a fully custom Canvas 2D implementation. This CSS import is an unused leftover and can be removed.
Defined in css/tokens.css and consumed as CSS custom properties throughout:
| Token | Dark | Light |
|---|---|---|
| Base background | #07070A |
#F8F8FB |
| Surface | #0D0D12 |
#F1F2F7 |
| Primary text | #F5F7FA |
#0F172A |
| Accent β purple | #7c3aed |
#7c3aed |
| Accent β indigo | #4f46e5 |
#4f46e5 |
| Accent β cyan | #06b6d4 |
#06b6d4 |
Why no framework or bundler? A portfolio site is a document with progressive enhancement, not an application. Adding React or Vue would mean shipping a runtime, a virtual DOM, and a hydration cost for what is ultimately a set of static sections. Vanilla JS with Intersection Observers and CSS custom properties achieves everything the design requires with a fraction of the payload and zero build-time complexity.
Why self-host fonts? Google Fonts embeds a tracking pixel that transmits the visitor's IP address to Google's servers on every page load. For a site that includes its own Privacy Policy and explicitly describes its data handling, using Google Fonts would be a direct contradiction. Self-hosting via @font-face adds two HTTP requests (woff2 files, cached after first load) with no privacy tradeoff.
Why Cloudflare Pages over GitHub Pages? Cloudflare Pages provides an automatic global CDN, edge caching, and HTTPS with no configuration β the same outcome as GitHub Pages but with better latency internationally, which matters for a portfolio viewed by recruiters across different regions.
Why a custom Canvas 2D world map instead of Leaflet? Leaflet is a feature-rich mapping library designed for interactive GIS applications with tile layers, zoom controls, and marker clusters. The use case here is a decorative, non-navigable map showing a handful of personal pins. Loading Leaflet (plus a tile provider) for this would be roughly 200 KB of JavaScript and thousands of tile requests for a purely aesthetic element. The custom Canvas implementation does the same visual job in under 150 lines of code and zero network requests beyond the GeoJSON border data.
mirconegri.com/
βββ index.html
βββ privacy.html # Privacy policy for this site
βββ lifeos-privacy.html # Privacy policy for the LifeOS companion app
βββ LICENSE
βββ css/
β βββ tokens.css # Design tokens, theme variables, reset
β βββ light-mode.css # Light theme overrides
β βββ layout.css # Scroll progress bar, aurora canvas, sections, footer
β βββ navbar.css # Fixed navbar and mobile menu
β βββ hero.css # Hero section and CTA buttons
β βββ sections.css # All content sections
β βββ fonts.css # Self-hosted @font-face declarations
β βββ extras.css # Film grain, intro screen, command palette
βββ js/
β βββ aurora.js # Animated gradient blob background
β βββ threejs-hero.js # 3D wireframe icosahedron
β βββ orb.js # Cursor-following morphing orb
β βββ ui.js # Navbar, theme/language toggle, scroll reveal, card tilt
β βββ intro.js # First-visit intro overlay
β βββ grain.js # Film grain overlay injection
β βββ map.js # Custom Canvas 2D world map
β βββ palette.js # Command palette
β βββ scrollytelling.js # Not loaded β see Features note above
βββ fonts/
β βββ inter/ # woff2 files referenced by fonts.css
β βββ jetbrains-mono/ # woff2 files referenced by fonts.css
βββ assets/
βββ CV_Mirco_Negri.pdf # Linked by the hero "Download CV" button
βββ lifeos-icon.png # Referenced by lifeos-privacy.html
βββ ... # Preview screenshots and GIFs used in this README
- Any modern browser
- A local HTTP server is optional but recommended to avoid any
file://protocol restrictions
git clone https://github.com/mirconegri/mirconegri.com.git
cd mirconegri.com
open index.htmlOr serve locally:
python3 -m http.server 8000
# then open http://localhost:8000No build step, no npm install, no compilation.
- Live: mirconegri.com
- Local: open
index.htmldirectly or via a local server as shown above
Deployed via Cloudflare Pages connected to this repository. Every push to main triggers an automatic deploy.
No .env file and no build-time environment variables. The only runtime integration points are:
| Item | Location | Notes |
|---|---|---|
| Formspree endpoint | index.html β <form action="https://formspree.io/f/xpqedpyo"> |
Tied to the author's Formspree account β replace the endpoint ID to reuse the form |
| CV file | assets/CV_Mirco_Negri.pdf |
Linked by the hero "Download CV" button β replace with your own file at this path |
| LifeOS icon | assets/lifeos-icon.png |
Referenced by lifeos-privacy.html |
This is a personal portfolio, but bug reports and suggestions are welcome:
- Fork the repository
- Create a feature branch (
git checkout -b feature/your-feature) - Commit your changes with a clear message
- Open a Pull Request
For broken links or layout issues, open an Issue.
Mirco Negri β Computer Science @ UniTrento
This project is licensed under the MIT License β see the LICENSE file for details.

















