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
Product preview

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 senior 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
- 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
- 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
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
- 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

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


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.
More case studies
All experiments
ExperimentEnergy tracking for better daily decisions
Daygrain is my current product experiment: a personal energy and decision-making app built around daily logging, Apple Health signals, and honest self-awareness.
ExperimentHolographic card carousel
Klarna card selector that feels like flipping through Pokémon trading cards — ghost-duplicate infinite scroll, pointer tilt, accelerometer-driven holo layers, and iOS haptic steps..
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.