All articles
11 · Design & interaction · 2026.10 · 5 min

From design prototype to production code: porting controlled chaos to Vue

This site did not start as code but as an interactive design prototype: devilyxrd, Controlled Chaos. In this note I describe how I ported that prototype page by page into Vue 3 and TypeScript, what I took over as it was, and what I added and changed afterwards.

01The starting point: a design canvas

The first form of the project was a design prototype. It was called Controlled Chaos, kontrollü kaos in Turkish. The prototype had pages, colours, typography and motion, and next to it a handoff board. That board described what kind of structure to build on the Vue side and which tool should drive which animation.

A prototype is a good tool for showing look and behaviour. But routing, SEO, accessibility and performance are not its job. My job was to add those without losing the look. I did it in order rather than all at once: tokens first, then structure, then the scenes, and finally the improvements.

02Tokens first

I started the port with the colours, because everything else depends on them. The first line of src/styles/tokens.css still points to the relevant section of the handoff. The palette has nine colours: Void #0B0C11 for the background, Graphite #171922 and Slate #292C38 for surfaces, Soft #EDECF2 for text, Iris #A8A1F1 as the accent, Mauve #C7A8BE as the hidden mode's colour, Dusty #8999BF as a secondary accent, Steel #8A8E9E for muted text and Border #363945 for lines. I added a --body tone for body text.

src/styles/tokens.css
:root {
  --void: #0B0C11;
  --graphite: #171922;
  --slate: #292C38;
  --soft: #EDECF2;
  --iris: #A8A1F1;
  --dusty: #8999BF;
  --mauve: #C7A8BE;
  --steel: #8A8E9E;
  --border: #363945;
  --body: #C9C8D3;

  --f-display: 'Archivo', 'Syne', 'Space Grotesk', sans-serif;
  --f-body: 'Space Grotesk', system-ui, sans-serif;
  --f-mono: 'IBM Plex Mono', ui-monospace, monospace;
}

/* hidden mode: footer x clicked three times */
.app.mv { --iris: var(--mauve); }

Typography is three families: Syne for headings, Space Grotesk for body text, IBM Plex Mono for labels and numbers. The variables are defined as --f-display, --f-body and --f-mono. A detail came later: an Archivo cut used only for the g and ğ glyphs sits at the front of the --f-display stack.

The benefit of putting tokens first showed up in hidden mode: a single line in .app.mv changes the entire accent colour. Had the colours been written into components one by one, that would have turned into a search-and-replace job.

03From pages to components

The prototype was thought of page by page; code should be organised by responsibility. I split the folders accordingly. views holds one file per route. features holds the large scenes that belong to a specific page: home page scenes such as HeroScene, MarqueeBand and ProjectIndex, and the eight playground experiments with the ExperimentShell that wraps them. components holds the pieces used everywhere: header, menu, route shutter, command palette, cursor, footer, wordmark and the procedural cover art. composables holds recurring behaviour: reduced motion, the pointer layer, route transitions, hidden mode.

Moving content out of the templates was a separate step. The project and article lists are typed files under src/content; the text itself lives in tr.ts and en.ts under src/i18n/locales. Adding an article does not require touching a component, and every key in both languages goes through type checking.

I did not add a library for state management. The state shared by the header, menu, palette, footer and views is a single reactive object in src/stores/ui.ts: whether the menu and palette are open, the toast text, the hidden mode counter, the scroll state. Most of the prototype's interactions could be reduced to these few fields; a bigger tool would not have found enough work to justify what it brings along.

A prototype says what will be seen; code says what can be maintained.

04Who owns the motion

Everything moved in the prototype. Moving to code, I wanted every motion to have an owner. The rule in the handoff was simple and I kept it: state changes with CSS and Vue transitions, the route shutter and hero entrance with GSAP, the geometry lab with Anime.js. Anime.js lives only in the lazily loaded /playground bundle; a comment in the router repeats this, and the home bundle never reaches it. GSAP was later also used for the shockwave and letter scatter in hidden mode.

This split may look like a list of rules, but its real benefit is in debugging. When something stutters, I know which layer to look at; there is no case where two libraries animate the same element at the same time.

05After the port

The first commit brought the prototype's nine routes, TR/EN text, the command palette, the full-screen menu and hidden mode. The real work started after that. On performance, I moved the hero out of Vue's reactivity into a loop that writes directly to the DOM, paused offscreen animations and moved the cursor to a native CSS cursor. On SEO, I started rendering every route to static HTML at build time, then moved English under /en and gave every page its own address in both languages. Finally I replaced the prototype's sample content with real projects, a real about text and real articles.

Looking back, the prototype helped most by reducing the load of decisions. Colour, spacing, typography and the language of motion arrived already thought through; in the code I could focus on architecture, boundaries and things that can be measured.

Related articles
  1. 12Moving from npm to pnpm, and why I kept TypeScript at ~5.9Engineering2026.105 min
  2. 13One platform, many sites: the content model in a multilingual CMSEngineering2026.095 min