Building petermarc.com: design, code, and AI in one repo

A full rebuild of my portfolio and services site: not a template swap, but a typed content layer, token-driven CSS, scroll-linked sections, and a component library shaped around how I actually pitch founder work, built iteratively in Cursor with the same stack I recommend to clients.

  • Product design
  • Front-end
  • Meta
petermarc.com, portfolio and case study system

Why rebuild

The old site did the job, but it couldn't show how I work now. I needed a place that reads like a lead product designer who ships, case studies with real metrics, a services page that filters founders quickly, interaction experiments that prove craft, and copy that doesn't need a call to explain what I do. The constraint was time: one person, no agency pipeline, no CMS admin. Everything had to live in the repo, deploy in minutes, and stay editable during client work.

The site is the case study.

Every pattern on petermarc.com, scroll reveals, portfolio filters, short-form work, is something I would defend in a client review.

Stack

App Router first. Server Components where possible, client components only when scroll, motion, or browser APIs require it. No database, portfolio data is TypeScript modules compiled at build time.

Core dependencies:

  • Next.js 16.2 (Turbopack dev + static export paths)
  • React 19, concurrent rendering, modern refs
  • TypeScript 5, strict types for portfolio entities
  • Tailwind CSS 4, @import tailwindcss + @theme inline tokens
  • Framer Motion 12, menu, reveals, scroll-linked opacity
  • Lucide, icon set for nav and UI chrome
  • Three.js / OGL, optional WebGL backgrounds (hero experiments)
  • @calcom/embed-react, intro booking embed

Architecture

The repo splits into three layers: routes under `src/app`, presentation under `src/components`, and content under `src/lib`. Portfolio case studies and experiments are data, not MDX, that keeps layouts consistent and lets TypeScript enforce block types (pull quotes, flow compares, component grids) inside long-form narratives.

  • `src/app`, App Router pages, layouts, API routes (`/api/contact`, game leaderboard), sitemap and robots
  • `src/components/ui`, primitives: Button, TextLink, Pill, PageGrid, SectionHeader, Reveal
  • `src/components/portfolio`, CaseStudyTemplate, ShortPortfolio, FilterChips
  • `src/components/effects`, pixel backgrounds, cursor dot, decrypted text, gradient blinds
  • `src/lib/content.ts`, marketing copy for homepage, services, about (single source for nav labels)
  • `src/lib/portfolio/`, case-studies.ts, explorations.ts, short-portfolio.ts, portfolio-order.ts
  • `src/lib/feature-flags.ts`, build-time toggles (logo loader, button icons, layout experiments)
  • Content-in-code over CMS, faster iteration, full git history, no auth surface
  • Shared CaseStudySectionBlock for case studies and experiments, one narrative renderer
  • Optional `demo` on experiments, live stage for holo-cards / icon-burst; cover hero for write-ups
  • Portfolio filters as client-side route replace, no full page reload when switching chips

Design system

Tokens live in `src/lib/tokens.ts` and mirror into CSS custom properties in `globals.css`. Dark is the default page shell; white and warm bands alternate for long scroll pages. Section wrappers (`WarmSection`, `InvertedSection`) set `data-nav-surface` so the fixed nav inverts logo/menu contrast automatically.

  • Typography scale: `--text-hero` through `--text-small`, clamp-based editorial sizes, Inter Display for headings
  • Radius tokens: `--radius-chip`, `--radius-panel`, `--radius-card`, consistent softness site-wide
  • Spacing: `--page-gutter`, `--page-gap`, `--page-max`, PageGrid and Container align all sections
  • Button variants: primary / secondary / ghost / onDark, CSS variables per surface so secondary on dark is solid, not ghost
  • Pills & tags: shared `Pill` component; filter chips reuse tag styling with active ink fill

UI components

Primitives are intentionally boring, predictable API, no prop explosion. Composition happens at section level.

Key primitives and behaviour:

  • Button, pill CTA, optional trailing ArrowLinkIcon via `icon` prop, TextReveal hover on label
  • TextReveal, dual-line hover swap (menu links, masonry labels)
  • SectionHeader, eyebrow + display title + lead; optional DecryptedHeading
  • Reveal, Framer Motion fade/slide on scroll into view
  • PixelFrame, stepped 12-column SVG outline; content-height layout for stats and pricing cards
  • SeeAllWorkLink, underlined ↳ label + portfolio count, no pill chrome
  • PageGrid, 12-column editorial grid with col-span helpers
  • MediaCard, writing section cards with cover + tag + footer link

Portfolio system

Portfolio is the most complex surface: three presentation modes (card grid, full case study, vertical short portfolio) fed from one typed data layer.

  • ShortPortfolio, full-viewport vertical stack, scroll-synced index, fixed prev/next nav portaled to body, hash deep links (`/portfolio/short#klarna`)
  • ShortPortfolioStack, same slide cards at full width on the main portfolio index (extras not already in case study grid)
  • WorkMasonryGrid, homepage-only layout: featured 16:9, pair row, stagger with tall slot; mobile uniform 4:3
  • FilterChips, All / Short / Case studies / discipline / Experiments with live counts from `portfolioFilterCounts`
  • CaseStudyTemplate, inverted hero + pixel background, centered rail layout, sticky TOC, outcomes metrics, prev/next cards
  • CaseStudyTemplate, shared layout for case studies and experiments; live demo embed for interaction experiments

Order is data, not JSX.

`portfolio-order.ts` defines flagship slugs, card order, and short-portfolio slide order, reordering the homepage or index is a one-line array change, not a layout rewrite.

Motion & effects

Motion is functional first, orientation and feedback, not decoration. Every animated component checks `prefers-reduced-motion` and degrades to static state.

  • Nav, fullscreen clip-path menu, staggered link reveal, scroll lock while open
  • SprintSteps, scroll-driven line between [01]–[03] indices; text opacity tied to scroll progress via JS measurement
  • ServicesPricingTimeline (/startups), vertical 2px line centred in the section, card opacity and PixelFrame stroke emphasis synced to scroll
  • LogoLoader, first-visit-only intro (gated by sessionStorage + `NEXT_PUBLIC_LOGO_LOADER` on production)
  • CaseStudyPixelBackground / DisciplinesPixelBackground, canvas pixel grids tied to section tone
  • CursorDot, custom cursor on interactive `[data-cursor]` targets
  • DecryptedText / DecryptedHeading, scramble reveal for hero moments
  • 404 games, four mini-games with optional leaderboard API route
PixelBlast, WebGL pixel grid on the homepage hero with ripple interaction

Content model

Marketing copy and portfolio entities are separate. `content.ts` holds site-wide strings (nav, hero, services page, about testimonials). Portfolio entities extend a shared shape with `kind: 'case-study' | 'experiment'`.

Case study section blocks (composable in data):

  • pullQuote, editorial callout
  • flowCompare, before/after step lists
  • exploration, design variant comparison table
  • componentGrid, bullet grid for systems / stacks
  • decisions, numbered decision list
  • changelog, grouped release notes per version
  • beforeAfter, side-by-side narrative
  • researchMethods, method / answered pairs
  • image / video, inline media figures

Version control & deploy

GitHub repo with a staging branch for iterative work and main for production promotion. Vercel connects to the repo, preview deployments on every push, production on main merge.

  • Branch flow: feature work on `staging` → verify preview URL → merge to `main` when ready
  • Environment: `VERCEL_ENV=production` drives logo loader default via `next.config.ts`
  • Feature flags in `feature-flags.ts`, toggle button icons, case study layout, services sections without branch divergence
  • Static generation: 46 routes at build (`generateStaticParams` for case studies, explorations, services slugs)
  • API routes: contact form POST handler, game leaderboard, serverless on Vercel
  • Assets in `/public`, AVIF heroes, short-portfolio MP4 loops, exploration demo sprites
  • No CMS webhooks, content PRs are the publish pipeline
  • Staging branch avoids half-finished portfolio data hitting production
  • Typecheck + `next build` as the pre-ship gate (no separate CI yaml in repo yet)

Building with Cursor

Most files were written or refactored pair-programming with Cursor agents, not generated in one shot. The workflow that worked: narrow task, point at existing component conventions, run build, fix types, ship to staging.

  • AGENTS.md points agents at Next.js 16 docs in node_modules, avoids stale App Router assumptions
  • Small diffs, one section, one component, one content block per pass
  • Reuse before invent, Button, SectionHeader, CaseStudySectionBlock extended instead of forked
  • Browser preview CSS edits fed back into globals.css (short portfolio nav contrast, button padding symmetry)
  • Agent transcripts for long sessions, context for why a pattern exists
Cursor IDE, petermarc.com repo with agent-assisted edits and browser preview

Quality bar

Ship criteria for a personal site should match client work: accessible defaults, performance-aware media, and honest metadata.

  • Skip to content link, focus rings on interactive elements, aria labels on portfolio filters and short-portfolio nav
  • Reduced motion, menu, reveals, ticker, decrypted text all respect OS preference
  • Images, next/image with explicit dimensions; AVIF heroes; video posters for short portfolio loops
  • SEO, metadata in layouts, JSON-LD in homepage, sitemap.xml generated from portfolio slugs
  • AEO, `llms.txt` and structured copy so answer engines can cite case studies accurately
  • Performance, PageSpeed pass on desktop after lazy media, narrowed fonts, and deferred WebGL
  • Cal.com embed with theme tokens aligned to contact section
Answer-engine optimization, llms.txt and structured portfolio URLs for AI crawlers
PageSpeed Insights, desktop performance score after optimization pass

What shipped

A single repo that is portfolio, services pitch, contact funnel, and interaction lab. New case studies are a typed object and a hero image. New experiments can be a live demo or a long-form build log. The site demonstrates the same systems thinking I apply to client products, just with me as the only stakeholder.

Changelog

Numbered beta releases ship through the footer build credit.

v1.1.4

Nav menu, case study ToC, portfolio filters, and SEO polish.

  • Site menu — hamburger morphs to X; link navigation unlocks scroll and lands at page top
  • Case study ToC — fixed nav without scroll lock; hides over full-bleed media; no #section deep links
  • Portfolio filters — switch short / case studies / disciplines without full page reload
  • Startups pricing — engagement card content centered in PixelFrame
  • SEO — llms-full.txt, FAQ schema on /startups, breadcrumbs, AI crawler access in robots.txt

v1.1.3

Mobile layout fixes for pricing timeline and work masonry.

  • Pricing timeline cards — taller PixelFrame on mobile, stacked title and price
  • Work masonry — wider vertical gaps between portfolio entries on small screens

v1.1.2

Case study layout polish, impact metrics, and exploration UX.

  • Case study footer spacing and hash scroll offsets
  • Recommendations grid with compact multi-card layout
  • Impact uplift metrics on discount and mobile banking case studies
  • Exploration pages link to live demo from last section
  • Changelog CornerDownRight icons; flow diagram white badges

v1.1.1

Case study polish, client recommendations, and metadata hardening.

  • Recommendations
  • Links
  • Case study anchors
  • Open Graph

Beta v1.1.0

First bump after the initial public beta (v1.0.0).

  • PixelFrame — stepped 12-column outline (Red Dot–style corner cuts), 2px stroke, content-driven height, measure-before-show so borders do not flash on load
  • About stats — credentials in PixelFrame cards, scroll-triggered stat counters, padding tuned after layout measurement
  • About — Selected awards row (Red Dot, iF, Awwwards, App Store Best of); designer icon on experience header
  • Startups pricing — tiers in PixelFrame panels, icons tinted to frame stroke, scroll-driven vertical timeline (centred 2px line behind cards, content opacity + border emphasis on scroll)
  • Work masonry — per-card hover modes (video, dim, swap); more space before the work grid on /startups
  • Case studies — Sources footer with award badges; inline markdown for external links in body copy
  • Analytics — Google Analytics 4 on production, including case study view events

Have a product flow that feels harder to explain than it should?

Send me your product, MVP, deck, or prototype. I'll tell you where I'd start.