[{"data":1,"prerenderedAt":227},["ShallowReactive",2],{"work-en-bereal":3,"work-cards-en":140},{"id":4,"title":5,"approach":6,"body":62,"brief":69,"card":75,"description":66,"extension":79,"gallery":80,"hero":105,"heroAlt":106,"meta":107,"navigation":108,"outcome":109,"path":113,"problem":114,"reflection":117,"resources":118,"role":126,"seo":135,"status":136,"stem":137,"summary":138,"__hash__":139},"work_en\u002Fwork\u002Fbereal.md","Re-coding BeReal",[7,25,38,50],{"label":8,"title":9,"prose":10,"artifacts":13},"Capture","Both cameras, no retakes",[11,12],"The front-end is a mobile-first PWA, and its defining moment is capture. When the prompt fires, both the front and rear cameras open at once: the rear shows what you are doing, the front shows your reaction, and the post carries your location. There are no filters and no retakes, because the constraint is the whole point of BeReal.","I built that capture screen and the submission flow behind it: opening both camera streams, composing the two frames into one post, and handing it to the API. The interesting work is in the constraints, getting two simultaneous camera streams to behave on a phone, and refusing the affordances (filters, retakes) that every instinct says to add.",[14,20],{"src":15,"alt":16,"caption":17,"decision":18,"width":19},"\u002Fimages\u002Fwork\u002Fbereal\u002Fartifact-capture-live.jpg","The capture screen mid-shot on a dark mobile interface: the front camera fills the frame with a live selfie, with a round shutter button and a flip-camera control below, under the BeReal header and a countdown.","Capture — both cameras live, no filters.","Opening both camera streams at once and refusing retakes is the product, so the capture screen exposes only a shutter and a flip control.","half",{"src":21,"alt":22,"caption":23,"decision":24,"width":19},"\u002Fimages\u002Fwork\u002Fbereal\u002Fartifact-capture-post.jpg","The composed post ready to send: the rear photo full-frame with the front-camera selfie inset in the top corner, and a send button (Envoyer) below.","Compose — rear frame, front inset, one post.","The two frames compose into a single post before upload, so the dual photo is one object the rest of the app can treat as a unit.",{"label":26,"title":27,"prose":28,"artifacts":31},"Pipeline","Memory to CDN, never to disk",[29,30],"On the back-end the photo arrives as an upload and has to be stored somewhere fast and cheap. I built the pipeline around Multer's in-memory storage, so an uploaded image is held as a buffer rather than written to the server's disk, and streamed straight to Cloudinary with an upload stream.","Keeping the file in memory and streaming it out means no temporary files to clean up, no disk to fill, and a server that stays stateless. The endpoint accepts the two camera images together (front and back), so the dual photo is a first-class shape in the API, not two unrelated uploads.",[32],{"src":33,"alt":34,"caption":35,"decision":36,"width":37},"\u002Fimages\u002Fwork\u002Fbereal\u002Fartifact-pipeline.jpg","A four-step pipeline diagram: a multipart upload of frontCamera and backCamera, held in memory by Multer as a buffer (marked no temp file, no disk), streamed to Cloudinary with upload_stream, then stored as URLs on the publication in MongoDB.","The upload pipeline, memory to CDN.","Holding the upload in memory and streaming it straight to the CDN keeps the server stateless, with no temporary files to clean up.","wide",{"label":39,"title":40,"prose":41,"artifacts":44},"Transform","Cropping by content and by face",[42,43],"Storage is also where the images get shaped, and I let the CDN do the heavy lifting. The main photo is auto-cropped with content-aware gravity to a consistent height, and profile pictures use face-aware cropping (Cloudinary's face gravity) into square thumbnails, so a portrait stays centered on the face instead of the corner of a room.","Offloading the transforms to Cloudinary kept the API thin and the results consistent: format conversion, sizing, and smart cropping all happen on delivery, driven by a few transformation options rather than image-processing code I would have had to maintain.",[45],{"src":46,"alt":47,"caption":48,"decision":49,"width":37},"\u002Fimages\u002Fwork\u002Fbereal\u002Fartifact-transform.jpg","Two Cloudinary upload-option blocks side by side: the publication image uses content-aware gravity auto at a 3:4 ratio, while the profile picture uses face-aware gravity auto:faces with a round 1:1 crop, both gravity lines highlighted.","Two crops, set by a few options.","Content-aware gravity for posts and face-aware gravity for avatars come from one block of options each, so the CDN does the cropping and the API stays thin.",{"label":51,"title":52,"prose":53,"artifacts":56},"Tests","Proving the API actually works",[54,55],"Because the pipeline is the product, I covered it with integration tests rather than trusting it by hand. A Jest and Supertest suite hits the real endpoints (authentication, publications, and the rest) and asserts on the responses, so a change that breaks upload or feed visibility fails loudly instead of silently.","Writing those tests is also what made me a more careful reviewer on the team. Once the API had a safety net, code review could focus on design rather than on catching regressions by eye.",[57],{"src":58,"alt":59,"caption":60,"decision":61,"width":37},"\u002Fimages\u002Fwork\u002Fbereal\u002Fartifact-tests.jpg","The Jest coverage report summary: 77.42% of statements, 68.69% of branches, 75% of functions and 78.79% of lines covered, with a per-area table for controllers, middlewares, models and routes.","Coverage across the API, generated by Jest.","Covering the pipeline and the endpoints with integration tests means a change that breaks upload or feed visibility fails loudly, not silently.",{"type":63,"value":64,"toc":65},"minimark",[],{"title":66,"searchDepth":67,"depth":67,"links":68},"",2,[],{"role":70,"year":71,"host":72,"scope":73,"shipped":74},"Image pipeline (front + back)","2024","HEIG-VD · DévMobil + ArchiOWeb","Team of four","Vue PWA + Express REST API",{"title":76,"alt":77,"image":78},"Re-coding BeReal — a dual-camera capture and image pipeline","Re-coding BeReal: the composed dual-photo post beside a friend's feed, on a dark mobile interface.","\u002Fimages\u002Fwork\u002Fbereal\u002Fadjacent.jpg","md",[81,85,89,93,97,101],{"src":82,"alt":83,"caption":84},"\u002Fimages\u002Fwork\u002Fbereal\u002Fgallery-feed.jpg","The news feed on a dark mobile interface: a friend's dual photo shown full-width, a landscape rear shot with the selfie inset, under the app header and above a bottom navigation bar.","Feed — a friend's post, infinite scroll.",{"src":86,"alt":87,"caption":88},"\u002Fimages\u002Fwork\u002Fbereal\u002Fgallery-gate.jpg","The feed blurred behind a centered prompt reading Poste pour voir, telling the user to post their own BeReal before they can see their friends' posts.","Honest by reciprocity — post to see.",{"src":90,"alt":91,"caption":92},"\u002Fimages\u002Fwork\u002Fbereal\u002Fgallery-comments.jpg","A post opened on its detail screen with a comment thread below, showing replies and a comment input field.","Comments and replies on a post.",{"src":94,"alt":95,"caption":96},"\u002Fimages\u002Fwork\u002Fbereal\u002Fgallery-profile.jpg","A user profile showing a round avatar, a pseudonym, counts for publications, comments and friends, and a grid of recent BeReals.","Profile — stats and recent BeReals.",{"src":98,"alt":99,"caption":100},"\u002Fimages\u002Fwork\u002Fbereal\u002Fgallery-friends.jpg","The friends screen: a search field and a list of friends with round avatars, plus a tab for pending friend requests.","Friends and requests.",{"src":102,"alt":103,"caption":104},"\u002Fimages\u002Fwork\u002Fbereal\u002Fgallery-swagger.jpg","The Swagger UI for the REST API, listing endpoint groups for Auth, Users and Publications with color-coded GET, POST, PUT, PATCH and DELETE methods.","The REST API, documented with Swagger.","\u002Fimages\u002Fwork\u002Fbereal\u002Fhero.jpg","Three screens from the BeReal re-code on a dark mobile interface: the live dual-camera capture, the composed post with the rear photo full-frame and the front-camera selfie inset, and a friend's feed.",{},true,[110,111,112],"A working two-part BeReal re-code: a mobile-first Vue PWA and an Express and Mongoose REST API, with the dual-photo flow working end to end, from capture to a friend's feed.","An efficient image pipeline, in-memory upload streamed to a CDN with content- and face-aware cropping, and a Jest and Supertest integration suite covering the API it runs on.","The honest-feed rule intact: you only see your friends' posts on a day you have posted yourself, which is the social mechanic that makes BeReal what it is.","\u002Fwork\u002Fbereal",[115,116],"BeReal's entire product is a single honest photo: both cameras fire at a random daily prompt, no filters, no retakes. Re-coding it means the photo is not a feature, it is the product, and every technical decision has to serve that one unfiltered image.","The project ran across two HEIG-VD courses, a mobile PWA and a REST API, and I took the part that runs straight through both: the image pipeline, from the moment both cameras fire to the moment the stored, cropped photo reappears in a friend's feed.","What stuck most was the discipline of covering the part that matters most with integration tests, so the rest of the team can move without fear of breaking it. Beyond that, this was the first time I built a complete web application end to end, front to back, with Vue, Express, and Mongoose. Following the full development process, testing and all, is what made the theory land: it turned what we were learning about NoSQL and Express into a real, working app.",[119,123],{"type":120,"title":121,"url":122},"github","Front-end — Vue mobile PWA","https:\u002F\u002Fgithub.com\u002FHEIG-COMEM\u002FHEIG-VD_DevMobil_REST",{"type":120,"title":124,"url":125},"Back-end — Express REST API","https:\u002F\u002Fgithub.com\u002FHEIG-COMEM\u002FHEIG-VD_ArchiOWeb_REST",{"led":127,"contributed":130,"notTouched":132,"team":134},[128,129],"The dual-camera capture screen and the full submission flow on the front-end","The image upload and storage pipeline on the back-end, plus the integration tests for it",[131],"Code review and code quality across the REST API",[133],"The rest of the front-end views and the friend and notification systems, built by teammates","Team of four for the DévMobil and ArchiOWeb courses at HEIG-VD: Jérémie Zurflüh, Jérémy Martin, and Antoine Uldry (front-end and back-end developers), and Steve Pasche (UX\u002FUI design).",{"title":5,"description":66},"live","work\u002Fbereal","A partial re-code of BeReal across two courses: a mobile-first Vue PWA and an Express REST API. I owned the image-handling pipeline end to end, from firing both cameras at once on the front to uploading, cropping, and storing the photo on the back.","AkXKUBtGgs7CIcnDImRsUSr70xE3KyGJ6UwquDXYpxM",[141,147,153,159,161,167,173,179,185,191,197,203,209,215,221],{"path":142,"card":143},"\u002Fwork\u002Fa-ta-dispo",{"title":144,"alt":145,"image":146},"À ta Dispo — matching event volunteers with organizers","The hand-drawn À ta Dispo wordmark, a coral logo on a pink sticker shape, on a plain coral background.","\u002Fimages\u002Fwork\u002Fa-ta-dispo\u002Fadjacent.jpg",{"path":148,"card":149},"\u002Fwork\u002Fan-aura-of-words",{"title":150,"image":151,"alt":152},"An Aura of Words — Lugano's parks, told by their reviews","\u002Fimages\u002Fwork\u002Fan-aura-of-words\u002Fadjacent.jpg","Lugano's five parks shown on a map, each as a color-blended organic aura sized by review volume.",{"path":154,"card":155},"\u002Fwork\u002Fbeau-rivage",{"title":156,"alt":157,"image":158},"Beau-Rivage Eco-Redesign — a 95% lighter luxury hotel site","The redesigned Beau-Rivage Palace site: restrained, elegant, far lighter than the original.","\u002Fimages\u002Fwork\u002Fbeau-rivage\u002Fadjacent.jpg",{"path":113,"card":160},{"title":76,"alt":77,"image":78},{"path":162,"card":163},"\u002Fwork\u002Fbrushbuddy",{"title":164,"image":165,"alt":166},"BrushBuddy — a monster cube that guides two-minute toothbrushing","\u002Fimages\u002Fwork\u002Fbrushbuddy\u002Fadjacent.jpg","A hand lifts the toothbrush from the dock on top of the BrushBuddy cube, which has a worried yellow monster face.",{"path":168,"card":169},"\u002Fwork\u002Fcultural-trails",{"title":170,"alt":171,"image":172},"Cultural Trails — a full-stack heritage walking app","Two screens from the Sentiers culturels app on the project's green, peach and blue trail ribbon: the explore map of trails across the Lausanne shore, and in-walk navigation following a route on a satellite map.","\u002Fimages\u002Fwork\u002Fcultural-trails\u002Fadjacent.jpg",{"path":174,"card":175},"\u002Fwork\u002Fdatabloom",{"title":176,"image":177,"alt":178},"DataBloom — making the energy impact of digital usage visible","\u002Fimages\u002Fwork\u002Fdatabloom\u002Fadjacent.jpg","The DataBloom flower: ultramarine felt petals on a green segmented stem in a small terracotta pot, on a white studio backdrop.",{"path":180,"card":181},"\u002Fwork\u002Felen",{"title":182,"image":183,"alt":184},"ELEN — a speculative camera for invisible wireless presences","\u002Fimages\u002Fwork\u002Felen\u002Fadjacent.jpg","ELEN, a handheld camera with a translucent orange body and twin white handles, lit on a gray studio backdrop, its deep-blue screen showing through the plexiglass.",{"path":186,"card":187},"\u002Fwork\u002Ffamily-space",{"title":188,"image":189,"alt":190},"UBS Family Space — a family's shared place to manage money","\u002Fimages\u002Fwork\u002Ffamily-space\u002Fadjacent.jpg","UBS Family Space concept: two phone screens on a soft red gradient, a parent's hub for the child's analytics, limits, and goals, and the child's savings-goal screen.",{"path":192,"card":193},"\u002Fwork\u002Fhuman-loci",{"title":194,"image":195,"alt":196},"Human Loci — a synesthetic listening object","\u002Fimages\u002Fwork\u002Fhuman-loci\u002Fadjacent.jpg","Human Loci: a transparent acrylic listening box with three plaster disks for a park, a marina, and a train station.",{"path":198,"card":199},"\u002Fwork\u002Fmeeting-pond",{"title":200,"alt":201,"image":202},"Meeting Pond — connected light objects for distant presence","Two connected Meeting Pond units standing side by side, one glowing blue and the other pink, each showing ripples across a 32×32 LED grid.","\u002Fimages\u002Fwork\u002Fmeeting-pond\u002Fadjacent.jpg",{"path":204,"card":205},"\u002Fwork\u002Fthea",{"title":206,"image":207,"alt":208},"Thea — a calm new sense for living with allergies","\u002Fimages\u002Fwork\u002Fthea\u002Fadjacent.jpg","The Thea wearable suspended in a lattice of orange cords against a cobalt sky, its oblong gauge glowing, an editorial shot from the final presentation.",{"path":210,"card":211},"\u002Fwork\u002Fuefa-female-coaches",{"title":212,"alt":213,"image":214},"Why Not You? — the UEFA female coaches campaign","The Why Not You? campaign: bold display typography over a female coach, with the line Lead Your Passion, Shape the Game.","\u002Fimages\u002Fwork\u002Fuefa-female-coaches\u002Fadjacent.jpg",{"path":216,"card":217},"\u002Fwork\u002Fwama",{"title":218,"image":219,"alt":220},"Wama — machine learning as a sink-side witness","\u002Fimages\u002Fwork\u002Fwama\u002Fadjacent.jpg","Wama's built sink rig at an angle: a tap pouring into a clear basin, the phone in its laser-cut holder showing the character.",{"path":222,"card":223},"\u002Fwork\u002Fwematch",{"title":224,"image":225,"alt":226},"WeMatch — an AX matching service for the WeRoad world","\u002Fimages\u002Fwork\u002Fwematch\u002Fadjacent.jpg","WeMatch title card: 'WeMatch finds your group before you find your trip' in coral and ink on a soft white-to-pink gradient, with the round Matchy agent character.",1783535586865]