Personal portfolio website for Eduard Rotaru — a full-stack developer and digital creative. Designed with a deliberate terminal/hacker aesthetic, featuring 3D model integration, interactive project case studies, and a cohesive pentagon-based visual identity.
Live site: eduardrotaru.ca
- The source of truth for portfolio case studies is
src/data/projects.js. - The contact form posts to
VITE_FORMSPREE_ENDPOINT; there is no hardcoded endpoint in the source tree. - Historical audit notes live in
CODE_AUDIT.mdandCODE_AUDIT_REPORT.md. - There is no separate
OPTIMIZATIONS.mdfile in the current workspace.
| Category | Tools |
|---|---|
| Framework | React 19 (JSX, no TypeScript) |
| Build | Vite 7 |
| Styling | Tailwind CSS 4 + tw-animate-css + shadcn/tailwind |
| UI Primitives | shadcn/ui (New York style) |
| Animation | Framer Motion 12 |
| 3D Rendering | @google/model-viewer 4 |
| Icons | lucide-react, react-icons (Simple Icons) |
| Routing | React Router DOM 7 |
| Deploy | Vercel (SPA rewrites) |
| Linting | ESLint 9 |
# Install dependencies
npm install
# Start dev server
npm run dev
# Production build
npm run build
# Preview production build
npm run preview
# Run linter
npm run lint- Node.js 18+
- npm (or pnpm / yarn)
src/
├── App.jsx # BrowserRouter with 5 lazy routes
├── main.jsx # Entry point (ReactDOM.createRoot)
├── index.css # Tailwind v4 imports, theme tokens, utility classes
├── components/
│ ├── Header.jsx # Fixed nav, scroll-aware backdrop, mobile menu
│ ├── Footer.jsx # Brand, nav links, social icons
│ ├── Loader.jsx # Suspense fallback
│ ├── SectionWrapper.jsx # Section shell with pentagon clip + scroll animation
│ ├── SectionLabel.jsx # Branded section header banner
│ ├── TerminalBar.jsx # Terminal-style top bar used in cards and forms
│ ├── TerminalText.jsx # Typewriter / terminal text effect
│ ├── TypedText.jsx # Accessible typed text component with variants
│ ├── ModelLodCard.jsx # Lazy-loaded 3D model viewer
│ ├── ProjectsAccordion.jsx # Horizontal accordion carousel
│ ├── ProjectsAccordion.css # Accordion-specific styles
│ ├── FormField.jsx # Shared contact form field wrapper
│ └── ui/
│ ├── HexPattern.jsx
│ ├── badge.jsx
│ ├── button.jsx
│ ├── input.jsx
│ ├── label.jsx
│ └── textarea.jsx
├── pages/
│ ├── Home/
│ ├── About/
│ ├── Projects/
│ ├── ProjectDetail/
│ └── Contact/
├── hooks/
│ ├── useFocusTrap.js # Keyboard focus trap for overlays and menus
│ ├── usePageMeta.js # Dynamic title, meta, OG/Twitter tags, canonical
│ ├── useStructuredData.js # JSON-LD structured data injection
│ └── useWindowWidth.js # Shared viewport-width hook for responsive effects
├── data/
│ ├── config.js # PERSONAL, SOCIAL, NAV_LINKS constants
│ └── projects.js # allProjects array (4 projects)
├── lib/
│ ├── helpers.jsx # Shared icon map and paragraph rendering helpers
│ └── utils.js # cn() helper combining clsx + tailwind-merge
└── ... # Route modules live under the page folders above
| Path | Page | Sections |
|---|---|---|
/ |
Home | Hero with animated portrait ring, featured projects accordion, tech stack grid |
/about |
About | Bio hero, 3D core values, differentiation, internship goals, mission statement |
/projects |
Projects | Tag-filtered project grid (All / Full-Stack / Creative / E-Commerce / API) |
/projects/:slug |
Project Detail | Deep-dive with The Why, The System, The Soul + screenshot modal |
/contact |
Contact | Contact form (Formspree), availability status, social links |
| Token | Hex | Usage |
|---|---|---|
| Cyber Cyan | #2dd4bf |
Primary accent, highlights, active nav |
| Soft Blue | #22b8c7 |
Secondary accent, alternate section banners |
| Charcoal | #1e1e1e |
Primary text, dark backgrounds (section wrappers) |
| Snow | #f9f7f7 |
Page background, light surfaces, text on dark sections |
| Muted | #666666 |
Secondary text, captions |
| Warm Gray | #eeece9 |
Footer and alternate surfaces |
| Text Dark | #333333 |
Body copy |
| Muted Light | #999999 |
Softer labels and metadata |
| Text Dim | #aaaaaa |
Low-emphasis text |
| Accordion BG | #19535f |
Active accordion panel background |
| Card BG | #f5f3f0 |
Card backgrounds |
- font-sans — Cal Sans (headings, display text)
- font-mono — Courier Prime (code, terminal output, badges)
- font-silom — Silom (uppercase labels, branding accents)
- Section backgrounds clipped to a pentagon shape (
polygon(0 0, calc(100% - 48px) 0, 100% 50%, calc(100% - 48px) 100%, 0 100%)) - Section headers use
SectionLabel: a cyan right-arrow triangle followed by a banner with an arrow-tipclipPath - Terminal card motif: three muted dot circles + monospace filename in a top bar with bottom border
- Hover interactions on links scale via
transform: scale(1.15)with React state tracking - Scroll-triggered animations via Framer Motion
whileInViewwith staggered delays
Three interactive .glb 3D models displayed via the <model-viewer> web component:
| Model | File | Associated Value |
|---|---|---|
| Skateboard | public/assets/3d/skateboard_-_used.glb |
The Art of Resilience |
| MacBook | public/assets/3d/macbook_air_notebook_pbr.glb |
Uncompromising Detail |
| MIDI Keyboard | public/assets/3d/midi_keyboard__piano__instrument.glb |
Intentional Craftsmanship |
All models auto-rotate, support click-and-drag orbit controls, and pause rotation 3 seconds after the last user interaction. The ModelLodCard component lazy-loads both the model library and the 3D asset using IntersectionObserver with a 300px root margin.
| Project | Category | Stack | Links |
|---|---|---|---|
| Nobz Beats | Music Platform | React, Vite, Convex, Tailwind | Live · Repo |
| MD Murals | Art Portfolio | React, Vite, GSAP, Convex | Live · Repo |
| Ollie North Skateshop | E-Commerce | Next.js 15, React 19, Convex, Clerk, Stripe | Live · Repo |
| Audio Tools API | Backend API | Flask, Python, FFmpeg, Demucs, PyTorch | Repo |
Each project detail page follows a three-part narrative:
- The Why — motivation and problem being solved
- The System — technical architecture and implementation details
- The Soul — design decisions, aesthetic choices, and philosophical approach
npm run dev # Start Vite dev server (default: http://localhost:5173)
npm run build # Production build → dist/
npm run preview # Preview production build locally
npm run lint # Run ESLint on the project
npm run og:generate # Generate Open Graph assets- Plugins:
@vitejs/plugin-react+@tailwindcss/vite - Path alias:
@→src/
- Style: New York, JSX only (
tsx: false) - CSS variables enabled, neutral base color
- Icon library: lucide
SPA rewrites — all paths redirect to index.html for client-side routing:
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}The contact form submits to import.meta.env.VITE_FORMSPREE_ENDPOINT. Set that environment variable before running the app.
- Fonts: Cal Sans by @tonsky, Courier Prime by Alan Greene, Silom (pixel/arcade style)
- 3D Models: Sourced from Sketchfab and similar open 3D repositories
- Icons: Lucide for UI icons, Simple Icons for brand/tech icons via
react-icons - shadcn/ui: UI primitives from shadcn
- Formspree: Contact form backend
- Email: eduard.rotaru89@gmail.com
- GitHub: nobz226
- LinkedIn: Eduard Rotaru
All rights reserved. This project is a personal portfolio and is not licensed for reuse or redistribution. Feel free to reference the architecture and patterns for inspiration.