Inspiration
I spend more time than I'd like to admit standing in front of my closet every morning. The information I need is actually all there — what the weather is doing, what I own, what kind of day I'm about to have — it's just never in one place. I wanted something that could hold all three at once and hand me a single answer instead of a list of options.
What it does
- Weather loads itself. On open, the app geolocates you and pulls current temperature, feels-like, and conditions from Open-Meteo.
- Your closet gets digitized. Upload photos of your clothes; a vision model tags each one with category, color, warmth (1–5), and style, and generates a matching pixel-art product image for it.
- You say how you feel. Pick from chips (Cozy, Sleek, Bold…) or type something specific like "first date, don't want to look like I tried too hard."
- You get one outfit, with reasoning. The model picks real items from your closet, explains why each piece was chosen, suggests 1–3 items you don't own that would complete the look (with shopping links), and renders a full-body figure wearing the result.
- It remembers. Every recommendation is saved, so the home screen becomes a scrollable log of what you wore and how you felt.
How I built it
React + Vite on the front end, Express on the back. Every keyed API call goes through the backend so nothing secret ends up in the browser bundle:
Browser (Vite + React) ├─ Open-Meteo ............... no key (weather) ├─ BigDataCloud ............. no key (reverse geocoding) └─ /api/* → Express ├─ /api/analyze Gemini vision → clothing tags ├─ /api/recommend Gemini → outfit JSON ├─ /api/generate-image Cloudflare Workers AI → pixel art └─ /api/search-images Google CSE → reference images
Every Gemini call uses responseMimeType: application/json with a
responseSchema, so there's no regex parsing or retry loop — the model can
only return the shape the app expects. The recommendation is then validated
server-side against the real closet, in case the model invents an item id that
doesn't exist.
Challenges I ran into
Most of the weekend went into one problem I didn't expect: getting AI-generated images at all. Six services, six different walls:
| Attempt | What happened |
|---|---|
| Pollinations.ai | Key-free URL endpoint from a tutorial. It had moved to a paid credit model; every request failed. |
Gemini gemini-2.5-flash-image |
429 quota exceeded on the first call. Image models have no free allocation at all, unlike Gemini's text models. |
Hugging Face FLUX.1-schnell |
410 — model deprecated and no longer supported by provider. |
Hugging Face via fal-ai |
400 — model not supported by provider. |
| Gemini, after adding billing | 402 — prepayment credits depleted. Adding a card converted the project to prepaid, which broke the text endpoints that were already working. Had to roll back to a fresh free-tier key. |
| Cloudflare Workers AI | Worked. Free daily allocation, no card required. |
Even after Cloudflare worked, flux-1-schnell rejects width, height, and
seed with a hard 400 (Additional or unevaluated properties not allowed). I
found the accepted request shape by elimination.
Accomplishments that I'm proud of
Everything in the flow actually works end to end — photo → tags → weather → mood → a real recommendation with reasoning, saved to history.
The part I'm most proud of isn't a feature, though. After the fourth image API failed, I stopped waiting on external services and wrote a local SVG pixel-art renderer that draws each garment from its color and category with zero network calls. It's still in the app as the fallback path, so a dead image API degrades one part of the UI instead of breaking the screen.
What I learned
- "Free tier" means very different things per provider. Text and image models on the same platform can have completely different billing rules.
- Negations in prompts can trip safety filters. Asking for a garment with
no person, no body, nobody wearing itwas rejected as NSFW (error 8007). Rewriting the same intent positively —laid out flat on its own as a catalog product sticker— passed immediately. - Model IDs need to be verified, not guessed.
gemini-3-flash404s;gemini-3-flash-previewworks. I stopped guessing and started queryingListModelsfor the real list. - Consistency across generated images can't be prompted into existence. Asking for "one light grey background" still returned cream, pale pink, and pale green. I fixed it in code instead: sample the corner pixels of each result, then repaint everything near that color to a single fixed tone.
- Every external call needs a fallback. This was the weekend's real lesson.
What's next for Digital Closet
- Recognizing multiple garments in a single photo, so scanning a whole closet doesn't mean one photo per item.
- Learning from history: if certain combinations keep getting picked and worn, weight them higher.
- Packing mode — given a destination and dates, pull the forecast and suggest what to bring.
Built With
- cloudflare-workers-ai
- express.js
- gemini-api
- google-custom-search
- javascript
- node.js
- open-meteo
- react
- vite
Log in or sign up for Devpost to join the conversation.