MoodReel AI-powered film and anime discovery matched to how you feel. 💡 Inspiration Every streaming service is organized the same way: by genre, by popularity, by what you watched last. But that's almost never how a person actually decides what to watch. You don't sit down thinking "I want a 1990s neo-noir thriller." You think "I want something hopeful after a rough week" or "cyberpunk neon, rain on glass, 2 a.m. energy." The gap between how we feel and how catalogs are indexed is where the endless doom-scroll lives — twenty minutes lost to the grid, and you close the app without pressing play. MoodReel started from a simple flip: what if the feeling was the search query? Not a genre filter, not a keyword — the actual mood, in your own words, or even just a photo of the room you're sitting in. 🎬 What it does MoodReel turns emotional intent into six personalized recommendations:

Natural-language mood input — type "a quiet film for a rainy Sunday" and go. Multimodal context — optionally upload an image (your room, your commute, your outfit) and GPT-5.6 Vision reads the visual mood alongside your text. Movies or anime/series modes for either catalog. Streaming results — recommendations render progressively as the model responds, each with a short description and an explanation of why it matches your mood. Cinematic UI — 3D hardware-accelerated tilt cards, live TMDB posters, and in-app YouTube trailer playback in a seamless modal. Vibe Ticket — export a shareable PNG that captures why a film fit your mood. Persistence — a local-storage watchlist and recent-mood history, no account required.

🛠️ How we built it The stack splits cleanly into a product and a promo. The web app is React 19 + TypeScript on Vite 8, styled with Tailwind CSS 4 and animated with Framer Motion, with an OGL/WebGL aurora background and parallax-tilt cards. The interesting architectural decision was keeping it secret-free on the client. All provider credentials live behind a small server (server/index.mjs) that owns three responsibilities: textBrowser → MoodReel server: /api/recommend and /api/tmdb/* → OpenAI (GPT-5.6) and TMDB Recommendations arrive over Server-Sent Events using a validated structured-JSON contract, so the UI can render useful cards before the full response lands. TMDB poster URLs and YouTube trailer IDs resolve through a same-origin proxy, and html-to-image rasterizes the Vibe Ticket to a downloadable PNG. Because judges shouldn't need an API key, we added a deterministic demo mode: when no key is configured, the server streams curated local recommendations through the exact same /api/recommend contract, and /api/health labels the runtime as demo. Same UI, same code path, zero credential risk. The promo video is a ~63-second piece built entirely in Remotion — programmatic video as React. It renders at 2560×14402560 \times 1440 2560×1440, 60 fps, across six scenes with narration, subtitles, and custom transitions. To keep animation authoring sane at 60 fps, we used a dual-framerate trick: everything is authored in 30 fps space and played back at 2×2\times 2× speed. frameauthor=⌊framecompSPEED⌋,SPEED=2\text{frame}{\text{author}} = \left\lfloor \frac{\text{frame}{\text{comp}}}{\text{SPEED}} \right\rfloor, \qquad \text{SPEED} = 2frameauthor​=⌊SPEEDframecomp​​⌋,SPEED=2 A global beat array drives a punch-in BeatCamera, anamorphic flares, white flashes, RGB-displacement glitch slices, and 2-frame "black breathers" at scene boundaries — so the visual accents stay locked to the ElevenLabs voiceover, whose character-level alignment we converted directly into subtitle timestamps. 📚 What we learned

Emotion is a better index than metadata. Once the feeling is the primary input, a language model with vision does something a genre filter fundamentally can't — it reasons about fit, and explaining "why it matches" turned out to be as valuable as the match itself. Stream early, validate always. SSE plus a strict JSON schema let us show progress in the first second while still guaranteeing the UI never renders a malformed card. Determinism is a feature, not just a demo convenience. Building demo mode against the real contract kept the app testable, judge-friendly, and honest about which path was live. Remotion rewards discipline. Every frame is a pure function of useCurrentFrame() — no wall-clock, no randomness at render time. Authoring at 30 fps and rendering at 60 kept motion buttery without doubling the animation math.

🧗 Challenges we ran into

Keeping credentials off the client while still supporting streaming meant moving everything behind a proxy and carefully normalizing provider errors so nothing leaked into responses or health checks. Progressive rendering without flicker — merging streamed recommendation chunks while preserving already-resolved posters and trailer keys took a careful reconciliation by movie identity, plus request-ID guarding so a new search can't be overwritten by a stale in-flight one. 60 fps Remotion renders are heavy. Render temp files and caches grow fast, so we redirected TEMP/TMP to a disposable local cache and separated render inputs/outputs from public/ to keep production builds lean. Syncing motion to voice — turning character-level ElevenLabs alignment into frame-accurate subtitles and beat cues, and giving each scene a "breathing tail" so it hands off to its transition instead of freezing on a silent frame.

🚀 What's next Live deployment with server-side credentials for full multimodal + trailer support, richer taste modeling from watchlist signals, and shareable mood "channels" so a vibe can become a playlist.

Built with GPT-5.6 Vision, TMDB, React, and Remotion. Movie metadata and imagery provided by TMDB; this product uses the TMDB API but is not endorsed or certified by TMDB.

Built With

Share this project:

Updates