Featured Project

Full Stack Engineer Portfolio

Full Stack Developer & Designer — Independently conceived, designed, and developed

LocationDenver, Colorado
Launch DateMarch 2026

The problem

Every project in this portfolio needed a home — but beyond that, building my own portfolio was an opportunity to explore technologies I had been curious about without client constraints, timelines, or inherited decisions. It was a deliberate space for self-directed experimentation.

Full Stack Engineer Portfolio Projects Manager Screenshot

My role

Everything. Brand identity, visual design, and full development — independently conceived and built.

The approach

Since this was my own platform, I made two deliberate choices: push into technologies I hadn't used in client work yet, and create a visual identity that felt genuinely personal rather than generic. For the identity, I designed a logo using the HTML symbols < and /> arranged to form a V — a nod to my last name, Vergara, and an immediate visual signal of what I do. That mark became the foundation for the palette, visual language, and overall design direction of the site.

Full Stack Engineer Portfolio Blender Screenshot

The solution

Built with Next.js and Payload CMS — the same stack I used on Sound Designer's portfolio, which I was refining further here. The standout technical element is a Three.js implementation of the logo in the navigation bar: a 3D model of the V mark that tracks the user's mouse as they move through the page. It's subtle, but it gives the site an original, living quality that a static logo wouldn't.

The site showcases my work and presents my resume, with Payload CMS handling content management so the portfolio stays easy to update over time.

1'use client'
2
3import type { Group, Texture } from 'three'
4import React, { useEffect, useRef } from 'react'
5
6interface Props {
7 size?: number
8 className?: string
9}
10
11export const Logo3D: React.FC<Props> = ({ size = 48, className }) => {
12 const mountRef = useRef<HTMLDivElement>(null)
13
14 useEffect(() => {
15 let cancelled = false
16 let cleanup: () => void = () => {}
17
18 const init = async () => {
19 const THREE = await import('three')
20 const { GLTFLoader } = await import('three/examples/jsm/loaders/GLTFLoader.js')
21
22 if (cancelled || !mountRef.current) return
23
24 const container = mountRef.current
25
26 const scene = new THREE.Scene()
27 const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100)
28 camera.position.set(0, 0.7, 2.2)
29 camera.lookAt(0, 0, 0)
30
31 const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true })
32 renderer.setSize(size, size)
33 renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
34 renderer.setClearColor(0x000000, 0)
35 container.appendChild(renderer.domElement)
36
37 const textureLoader = new THREE.TextureLoader()
38 const matcap = await new Promise<Texture>((resolve) => {
39 textureLoader.load('/matcap.png', (tex) => {
40 tex.colorSpace = THREE.SRGBColorSpace
41 resolve(tex)
42 })
43 })
44
45 const loader = new GLTFLoader()
46 const gltf = await new Promise<{ scene: Group }>((resolve, reject) => {
47 loader.load('/model.glb', resolve as never, undefined, reject)
48 })
49
50 if (cancelled) return
51
52 const model = gltf.scene
53 model.traverse((child) => {
54 if (child instanceof THREE.Mesh) {
55 child.material = new THREE.MeshMatcapMaterial({ matcap })
56 }
57 })
58
59 const box = new THREE.Box3().setFromObject(model)
60 const center = box.getCenter(new THREE.Vector3())
61 const modelSize = box.getSize(new THREE.Vector3())
62 const maxDim = Math.max(modelSize.x, modelSize.y, modelSize.z)
63 const scale = 1.7 / maxDim
64 model.scale.setScalar(scale)
65 model.position.sub(center.multiplyScalar(scale))
66 scene.add(model)
67
68 let targetX = 0
69 let targetY = 0
70 let currentX = 0
71 let currentY = 0
72
73 const handleMouseMove = (e: MouseEvent) => {
74 targetX = ((e.clientX / window.innerWidth) * 2 - 1) * 0.4
75 targetY = ((e.clientY / window.innerHeight) * 2 - 1) * 0.25
76 }
77 window.addEventListener('mousemove', handleMouseMove)
78
79 let raf: number
80 const animate = () => {
81 raf = requestAnimationFrame(animate)
82 currentX += (targetX - currentX) * 0.1
83 currentY += (targetY - currentY) * 0.1
84 model.rotation.y = currentX
85 model.rotation.x = currentY
86 renderer.render(scene, camera)
87 }
88 animate()
89
90 cleanup = () => {
91 cancelAnimationFrame(raf)
92 window.removeEventListener('mousemove', handleMouseMove)
93 renderer.dispose()
94 if (container.contains(renderer.domElement)) {
95 container.removeChild(renderer.domElement)
96 }
97 }
98 }
99
100 init().catch(console.error)
101
102 return () => {
103 cancelled = true
104 cleanup()
105 }
106 }, [size])
107
108 return <div ref={mountRef} style={{ width: size, height: size }} className={className} />
109}
110

What I learned

This was my entry point into Three.js and 3D rendering in the browser. Working without a client brief pushed me to make every decision — including ones I would normally defer or compromise on — which sharpened both my design thinking and my ability to own a project from concept to execution.