Repository navigation
v2.0.0: Next.js redesign
Overview
Version 2 is a full rebuild of kamycoding.com, the personal portfolio of Kamyar Zamanfar, in Next.js. The whole site is designed as a code editor: each section is a file, with a terminal, a command palette, line-drawing project covers and a cat. It ships in English and German, in a dark and a light theme, as static pages on Netlify. v1 (Angular) stays available at v1.0.0.
Highlights
- Editor design: file tabs as navigation, rulers, breadcrumbs and a status bar; sections as
index.tsx,work.tsx,about.md,stack.jsonandcontact.ts - Hero: typed headline, editor panel with TypeScript-style hover cards, and a terminal with 13 commands, history and Tab completion
- Work:
ls -laproject list with a pointer preview and a README drawer per project; line-drawing covers as SVG components - About: Markdown-style text, an ASCII portrait with a lens that reveals the photo, experience as a
git logand reviews as pull request approvals with links to the reviewers' LinkedIn profiles - Stack: foldable JSON tree, a commit heatmap of the v1 history and services as functions
- Contact: a
sendMessage({ ... })form with TypeScript-style errors, validated by the same schema in the browser and in the Netlify function - Global interactions: a
pnpm buildintro on load, command palette (Ctrl K/⌘ K), keyboard shortcuts and a custom cursor with action labels - English and German: English at
/, German at/de, browser language detection; a language switch keeps the reading position - Legal pages: legal notice and privacy policy in both languages; the privacy policy describes the language cookie and the theme setting
Contact form and backend
- No separate backend: the contact form is a Netlify Function with Brevo for email delivery (ADR 0002)
- One zod schema for the form and the function; unknown fields and control characters in the name are rejected
- Only JSON from the own origin is accepted (
415,403), bodies above 10 KB are rejected before parsing (413) - The Brevo request times out after at most 8 seconds
- Rate limiting (5 requests per 3 minutes per IP) and a honeypot that browser autofill leaves alone
- One log line per request (accepted with Brevo's message ID, honeypot, validation rule, rejection reason, provider status), never names, emails or message text
Security
- Enforced Content Security Policy with
upgrade-insecure-requestsand security headers: HSTS,X-Content-Type-Options,X-Frame-Options,Referrer-Policy,Permissions-PolicyandCross-Origin-Opener-Policy(ADR 0005) - No
X-Powered-Byheader; the language cookie is aSecuresession cookie security.txtand a security policy with GitHub private vulnerability reporting- CodeQL code scanning, Dependabot, secret scanning with push protection
- No tracking, analytics or third-party scripts
Quality
- CI on every push and pull request: frozen install, lint, type check, tests and build, with actions pinned to full commit SHAs
- Unit tests with Vitest for the contact function (including every request check and the privacy-safe logging), the shared schema, the scroll spy, the commit heatmap and the language switch position
- Accessibility: keyboard access everywhere, visible focus, reduced motion respected, labels that include the visible text, small text at WCAG AA contrast
- Metadata per language with canonical and language alternates, Open Graph images, a sitemap; legal pages are
noindex
Tech stack
Next.js 16, React 19, TypeScript, Tailwind CSS v4, next-intl, next-themes, Geist, zod, Vitest, Netlify Functions, Brevo, pnpm
Getting started
Requirements: Node.js 24 (.nvmrc) and pnpm (pinned in package.json, enabled through corepack).
git clone https://github.com/kamycoding/portfolio.git
cd portfolio
git checkout v2.0.0
corepack enable pnpm
pnpm install
pnpm devThe app runs at http://localhost:3000. The contact function needs the variables in .env.example and is tested end to end on Netlify Deploy Previews.
Full changelog
https://github.com/kamycoding/portfolio/blob/v2.0.0/CHANGELOG.md