Skip to content

// project

Portfolio Twin

The site you're reading, plus Panda the assistant that answers for me, and the whole thing rebuilt a second time in Compose Multiplatform, one commonMain to Web, Desktop, Android and iOS.

The brief

A CV that is also the portfolio piece. Rather than describe the work, the site is built the way the work is built, and then rebuilt a second time on an entirely different stack to see what survives the move. The React version renders everything from one TypeScript file of profile data, which is also what the AI assistant, the résumé, the OG images and the two /llms.txt files are generated from, so none of them can disagree with each other. The Compose twin transcribes that file by hand, which is a different contract and a weaker one.

What shipped

  • Two full implementations of one portfolio: the same content rendered by React on the web and by Compose Multiplatform to four targets, which makes the comparison concrete rather than theoretical.
  • Provider-agnostic chat backend (Groq / Gemini / Claude) with prompt-injection guards. Panda is grounded in this file, the same source of truth the pages render from, so the assistant cannot drift from the site.
  • Every claim on this site is checked mechanically before it ships, not recalled: a claim-audit script verifies the facts and scans every outward-facing surface for phrases already disproven.
cv-siddharthReact 19Vite 8Tailwind v4Vercel EdgeMulti-provider LLMKotlin MultiplatformCompose MultiplatformKotlin/Wasm
Live React source Compose Multiplatform source kmp-app-template kmp-family (this site's own case study) Live · React on Vercel, CMP across 4 targets
built on
kmp-build-logic
kmp-toolkit

// case study

The short version

The problem

Claiming Compose Multiplatform reaches the web is easy to assert and hard to check: a screenshot of a cross-platform build proves nothing that a screenshot of anything else doesn't.

The decision

Build the same portfolio twice on purpose: the React 19 original stays the tuned, production site, and a from-scratch Compose Multiplatform port renders the identical content from one commonMain to Kotlin/Wasm, Desktop, Android and iOS, both built on kmp-app-template, so the comparison is concrete rather than theoretical.

The result

The CMP twin actually runs, embedded live in the page you're reading, at an honest cost over the wire, bleeding-edge Kotlin and Compose Multiplatform versions included, so the trade between the two stacks is visible instead of argued.

14.7 MB the Wasm twin's honest cost

Evidence: The CMP twin actually runs, embedded live in the page you're reading, at an honest cost over the wire, bleeding-edge Kotlin and Compose Multiplatform versions included, so the trade between the two stacks is visible instead of argued.
Go deeper

Analyst view: Portfolio Twin draws on 9 stack entries and links to 4 outside sources.

23

surfaces · every one on the wall

3386

unit tests · 355 files

35.7k

lines of Kotlin · the CMP twin

14.7 MB

the Wasm twin's honest cost

// multiplatform

One codebase, every surface

Recorded app screens and, where available, the running build. Sample content inside a screen is separate from the source-linked project metrics.

https://darkpandawarrior.github.io/cv/portfolio-app/index.html Live
Live build preview

Live: the Compose Multiplatform build of this very site, compiled to Wasm and running in your browser. Same content, one commonMain, a different stack entirely.

// design notes

How it works

The React site: how it is put together

React 19 + Vite 8 + Tailwind v4, file-routed with TanStack Router and server-rendered, deployed on Vercel Edge. There is no CMS and no database: profile.ts is the single source of truth, and a set of prebuild generators derive everything else from disk: galleries from the screenshot folders, comparison sets, the sitemap, the RSS feed, OG images, llms.txt, and AVIF/WebP derivatives for every raster. Adding a screenshot is a file drop, never a list edit, which is the only reason the media stays honest as it grows.

The surfaces: a guide to what is where

The home page carries the case studies and experience. /resume is the printable résumé; /shipped is the shelf and timeline of apps that actually reached a store; /project/<slug> is a project breakdown like this one, including a slide-to-compare viewer for design work. Then the rooms, which are the point of the site as much as the CV is: /map is The 3D Storyboard, the projects as a constellation; /forge is The Particle Forge, physics on a canvas; /blueprint is a 3D walkthrough; /compose is a live Compose-subset interpreter that parses a snippet and renders real composables rather than a screenshot; /terminal and /playground are interactive toys; /pulse and /lab are instrument views over the site's own data. /loopdown is Notes From The Loop, /read/<slug> the writing itself, /excelsior a magazine archive, /ink The Board, seven years of games, mined, /chess a chess corpus, /weeb a hand-kept list read as evidence. /hire runs a job description against the documented experience and reports the fit, including the gaps.

Panda, and why it cannot oversell

A provider-agnostic assistant over Groq, Gemini and Claude with prompt-injection guards, running on Vercel Edge functions. It is grounded in this same profile data rather than a separate prompt-authored biography, so it cannot invent a role or a metric that the site does not also show. The job-description check is explicitly instructed to be straight about where the fit is weak. An assistant that oversells its own person is worth nothing to a recruiter, and the honesty is what makes the rest credible.

The Compose Multiplatform twin

35,670 lines of hand-written Kotlin across 72 files and four Gradle modules (cmp-shared, cmp-web, cmp-desktop, cmp-android, plus an Xcode project on top of the shared one) rendering the same portfolio to Kotlin/Wasm, Desktop, Android and iOS from one commonMain. The corpora it renders are generated into Kotlin from this repo's own data rather than transcribed, so the two versions cannot disagree about a number. Deliberately bleeding edge: Kotlin 2.4.20, Compose Multiplatform 1.13.0-alpha01, AGP 9.5.0-alpha06, Gradle 9.8.0-rc-2, with pre-release toolchains included. The question it exists to answer is where the edge actually is.

What the CMP build refuses to depend on

Almost nothing, by design: no navigation library, no DI framework, no markdown parser, no diagram renderer, no icon pack, no shader library. Routing, the Mermaid layout engine, the SkSL ambient wash, the SSE frame parser and every icon are hand-built on Compose and Ktor primitives. It is built on kmp-app-template, so it doubles as the proof that the template carries a real app to four targets rather than a hello-world.

Honest about the edges

The CMP port is an experiment and is described as one. Compose Multiplatform on Wasm reaches further than most people expect and still costs something real in bundle size and first paint against a React build that was tuned for exactly this. Both versions exist here so the trade is visible rather than argued.

// architecture

How it's built

One profile.ts: every surface derived from it

Built twice: one portfolio, two stacks

// under the hood

Tech stack

React site

  • React 19
  • Vite 8
  • Tailwind v4
  • TanStack Router + Start
  • three.js / R3F
  • Vercel Edge
  • Playwright
  • Vitest

Assistant

  • Groq
  • Gemini
  • Claude
  • SSE streaming
  • Prompt-injection guards

CMP twin

  • Kotlin Multiplatform
  • Compose Multiplatform
  • Kotlin/Wasm
  • Desktop (JVM)
  • Android
  • iOS
  • Ktor

// gallery

Screens (20)

Swipe or use the arrows · tap a screen to enlarge

See how everything connects →