Frontend
- Nuxt / Vue
- React 19
- Next.js 16
- TypeScript
- Design systems
- SSR / SSG
Architecture frontend, IA générative et rendu temps réel. Plutôt qu’une liste de mots-clés, une étude de cas que vous pouvez ouvrir et éprouver : ce site.
Une plongée 3D jouable, un agent vocal qui parle en mon nom (et le dit), et un profil statique pour qui est pressé. Conçu par Sami et développé avec des agents de code IA (Claude Code), de l’architecture aux shaders : de l’ingénierie assistée par l’IA, dirigée, relue et testée.
Le navigateur parle au modèle en WebRTC ; le serveur ne fait que signer la session. Les actions du modèle reviennent comme données, sont validées, puis appliquées à la scène.
// 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) { … });
Depuis 2021 : architecture frontend d’une plateforme SaaS B2B, plateforme développeurs (composants, SDK TypeScript, API) et produits intégrant l’IA. Engineering Lead depuis janvier 2026.
Profil en 30 s →