Frontend
- Nuxt / Vue
- React 19
- Next.js 16
- TypeScript
- Design systems
- SSR / SSG
Frontend architecture, generative AI and real-time rendering. Instead of a list of keywords, a case study you can open and stress-test: this site.
A playable 3D dive, a voice agent that speaks on my behalf (and says it is an AI), and a static profile for people in a hurry. Designed by Sami and built with AI coding agents (Claude Code), from architecture to shaders: AI-assisted engineering, directed, reviewed and tested.
The browser talks to the model over WebRTC; the server only mints the session. Model actions come back as data, get validated, then applied to the scene.
// The browser only sends its connection offer. Everything that matters
// (model, tools, voice, limits) is decided on the server, and the
// secret key never reaches the client.
const session = buildSession({ tools: allowlistedTools });
const answer = await relay(offer, session); // key stays server-side
return answer;
// A tool call is data, never authority: exact keys, enumerated values.
export function eventFromTool(name: unknown, args: unknown) {
if (!args || typeof args !== 'object' || Array.isArray(args)) return null;
const keys = Object.keys(args);
if (name === 'show_project' && keys.length === 1 && keys[0] === 'project')
return parseExperienceEvent({ type: 'showProject', project: args.project });
// … one branch per tool; anything else is rejected (null)
}
// run once, and never for a response the visitor interrupted
if (!this.cancelledResponses.has(event.response_id) && !this.handledCalls.has(event.call_id)) {
this.handledCalls.add(event.call_id);
const result = action ? this.callbacks.scene(action) : null;
this.send({ type: 'conversation.item.create', item: { type: 'function_call_output', … } });
}
// Pure core: (state, telemetry, events) -> new state. Testable in Node.
export function step(state, sites, input, events = []) { … }
// Tiny external store: the HUD subscribes with useSyncExternalStore,
// the 3D scene reads getState() inside useFrame — no 60 fps re-renders.
export function createGameStore(sites, storage) {
let state = initialGame();
const listeners = new Set<() => void>();
return {
getState: () => state,
subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); },
tick(input) { commit(step(state, sites, input)); },
};
}
// "Under water" means under the actual wave, not under y = 0.
export function swellHeight(x, z, time, motion = 1, detail = 1) {
const localY = -z;
const a = (x * .93 + localY * .36) * .19 - time * .95;
const b = (x * -.45 + localY * .89) * .39 - time * 1.35;
return (Math.sin(a) * .42 + Math.sin(b) * .17 * detail) * motion;
}
// The test reads the GLSL source and fails if the shader drifts:
assert.match(shader, /swell\.z\+=\(sin\(a\)\*\.42\+sin\(b\)\*\.17\*swellDetail\)/);
// 12 wave packets injected into three.js Water: only normals are bent,
// so ripples stay sharp whatever the mesh resolution.
for (int i = 0; i < 12; i++) {
vec4 r = ripples[i]; float age = rippleClock - r.z;
if (r.w <= 0. || age < 0. || age > 7.) continue;
vec2 delta = worldPosition.xz - r.xy; float d = length(delta) + .0001;
float x = d - age * 2.35;
if (abs(x) > 10. && d > 1.5) continue; // early out far from the front
float envelope = exp(-x * x / (.55 + age * 1.25)) * exp(-age * .62) * r.w / sqrt(1. + d * .35);
float k = 6.2 - min(age, 4.) * .7;
rippleSlope += delta / d * cos(x * k) * k * envelope * .11;
}
// Quality adapts to the device: after 6 s below 30 fps,
// the scene switches to its low tier (no post-processing, fewer instances).
if (!low && !perf.reported && delta < .12 && clock.elapsedTime > 3) {
perf.seconds += delta; perf.frames++;
if (perf.seconds > 6) {
perf.reported = true;
if (perf.frames / perf.seconds < 30) onSlow();
}
}
// The R3F tree is memoised: HUD updates never reconcile the 3D scene.
export default memo(function OceanScene(props) { … });
Since 2021: frontend architecture of a B2B SaaS platform, developer platform (components, TypeScript SDK, APIs) and AI-powered products. Engineering Lead since January 2026.
30-second profile →