Skip to content

Repository files navigation

Amitesh Anand — Desktop Portfolio

An interactive portfolio built to feel like a minimalist desktop operating system: a scattered desktop with real folders and files, draggable/resizable windows, a floating macOS-style dock, and light/dark themes — instead of a traditional scrolling page.

Live content lives entirely in src/data/portfolio.ts; the rest of the app just renders it.

Tech stack

Layer Choice Why
Framework Next.js 16 (App Router) + TypeScript SEO/metadata support and fast static output, with full client interactivity where needed
Styling Tailwind CSS v4 Utility-first, fast to iterate, first-class dark mode via a custom dark variant
Animation Framer Motion Window open/close/minimize/maximize springs, dock magnification, staggered badge reveals
State Zustand Window manager (open/close/focus/z-index) and theme, without prop-drilling
Icons lucide-react + react-icons (Simple Icons / Font Awesome) Generic UI icons + official brand logos for GitHub/LinkedIn/tech stack
Resume viewer Native <iframe> Uses the browser's built-in PDF viewer — no react-pdf/pdf.js worker configuration to fight in the App Router
Deployment Vercel Zero-config for Next.js

Getting started

npm install
npm run dev

Open http://localhost:3000.

npm run build   # production build
npm run start   # serve the production build
npm run lint    # ESLint

Project structure

src/
  app/
    layout.tsx          → root layout, metadata, theme-init script, font setup
    page.tsx             → renders Desktop + WindowManager + Dock
  components/
    desktop/             → Wallpaper, MenuBar, Desktop shell, DesktopIcon
    dock/                → Dock, DockIcon (magnification effect)
    window/              → Window (drag/resize/minimize/maximize), WindowManager
    apps/                → one component per window's content (About, Projects, Skills, ...)
                            + registry.tsx mapping appId → component
  data/
    portfolio.ts          → ALL content — personal info, experience, projects, skills,
                             education, achievements, desktop icon layout
  types/
    portfolio.ts          → TypeScript interfaces for the data above
  store/
    windows.ts             → open windows, positions, sizes, z-index, minimize/maximize
    theme.ts                → light/dark theme, persisted to localStorage
  lib/
    cn.ts                   → clsx + tailwind-merge helper
    useIsMobile.ts           → viewport breakpoint hook (drives mobile grid/fullscreen windows)
    techIcons.tsx            → tech-stack name → brand icon map
public/
  images/portrait.webp        → full original photo (used for the Open Graph / social preview image)
  images/avatar.webp           → pre-cropped headshot (About Me)
  images/portrait-cutout.webp   → background-removed cutout (wallpaper subject)
  resume.pdf                     → resume, served directly and via the Resume window

Customizing content

Almost everything on the site is data-driven. To update it:

  • Personal info, bio, experience, projects, skills, education, achievements — edit src/data/portfolio.ts. Each section is a typed array/object; add or remove entries and the UI updates automatically.

  • Desktop icon layoutdesktopItems in the same file controls which icons appear and their { x, y } position (percentages of the viewport) on desktop-sized screens. Mobile ignores these and uses a 4-column grid instead.

  • Notes appnotesContent, same file. Content supports a light markdown-like syntax: # heading, ## subheading, - bullet, [ ] checklist item, blank lines for spacing (rendered by TextDocument.tsx).

  • Photo: replacing it takes three steps since three derived assets depend on it —

    1. Replace public/images/portrait.webp with the new photo (same filename).
    2. Re-generate the About Me headshot crop:
      node -e "require('sharp')('public/images/portrait.webp').extract({left:1000,top:1000,width:2000,height:2000}).resize(400,400).webp({quality:85}).toFile('public/images/avatar.webp')"
      Adjust the extract region to frame the new photo's face — the current numbers are tuned for the current photo.
    3. Re-generate the wallpaper cutout (background removal). This needs the @imgly/background-removal-node package, which isn't a permanent dependency — install it temporarily:
      npm install --no-save @imgly/background-removal-node
      node -e "require('@imgly/background-removal-node').removeBackground('public/images/portrait.webp').then(async (blob) => require('fs').writeFileSync('public/images/portrait-cutout.webp', Buffer.from(await blob.arrayBuffer())))"
      npm uninstall @imgly/background-removal-node
      Note: on Windows, run this in its own node -e invocation separate from any sharp calls in the same process — the two packages' native modules conflict if loaded together in one process (a Windows DLL-loading quirk, not a real incompatibility). Run background removal first, then a separate sharp command if you need to resize/re-encode the result.

    The wallpaper background itself (the gradient behind the cutout) is pure CSS — see --wallpaper-gradient in src/app/globals.css — so it never needs regenerating, only the cutout does.

  • Resume — replace public/resume.pdf (same filename, no code changes needed).

Environment variables

Variable Purpose
NEXT_PUBLIC_SITE_URL Used for metadataBase (resolves absolute Open Graph/Twitter image URLs). Defaults to http://localhost:3000 in development — set this to your real deployed URL (e.g. on Vercel, as a project environment variable) once deployed.

Notes on scope

  • Project screenshots use category-icon placeholder headers rather than real product screenshots (none were provided) — swap in real images in ProjectsApp.tsx if you have them.
  • Only projects with a confirmed GitHub URL show a "Code" link — add a github field to a project in portfolio.ts to show one for it.

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages