Inspiration
Every café owner has had the 7am crisis: a key ingredient didn't arrive, three drinks on the board can't be made, and the line is already forming. The instinct is to Google "espresso drinks without oat milk" and pick the first result, but that's retrieval, not invention. We wanted to build something that computes an original drink from whatever is actually in the building today.
What it does
Palette is a flavor-space engine for cafés. You enter your available ingredients (and optionally their costs), mark anything out of stock, and flag a must-use item. Palette:
- Computes the gap: a deterministic farthest-point grid search finds the most under-served point in a 5-axis flavor space (sweetness, acidity, bitterness, richness, intensity) before the AI is ever called. You can watch the point land inside the labeled zone on a live scatter chart.
- Generates the drink: a Groq-powered LLM (llama-3.3-70b-versatile) invents an original recipe targeted directly at that gap, with a constraint compliance loop that verifies out-of-stock avoidance and must-use adherence in plain Python, not another LLM call grading itself.
- Prices it honestly: real arithmetic from your entered costs, falling back to a bundled reference price table, with a $0.50 floor for implausible recipes.
- Refreshes the whole menu: batch mode runs a greedy farthest-point search to spread 4 drinks across open flavor space simultaneously, with all 4 Groq calls running in parallel.
- Remembers what you keep: a persistent "kept menu" lets you build up your curated board across sessions, with a drinks-invented counter that survives page reloads.
How we built it
Backend (Python / FastAPI on Render)
- Deterministic gap search:
gap.pyruns a 20×20 farthest-point grid search over [1, 9]⁵ flavor space to find the point maximally distant from the existing menu. Batch mode uses greedy sampling so each new target spreads away from all previous ones. - Constraint compliance:
validation.pychecks recipe output with plain substring matching, retries once with a correction prompt listing the specific violations, and returns a warning (never an empty result) if violations persist after the retry. - Cost engine:
costing.pyparses quantity strings via regex (fraction-first to avoid decimal ambiguity), multiplies by a bundled reference price table, applies a $0.50 floor. - Parallel generation:
asyncio.gather+ThreadPoolExecutorruns all 4 batch Groq calls concurrently, cutting wall time from ~90s (sequential) to ~25s.
Frontend (React / Vite on Vercel)
- Recharts
ScatterChartwith a liveReferenceArealabeled "computed gap" showing exactly where in flavor space the new drink is targeted. - Framer Motion throughout: parallax landing page (4 depth layers via
useScroll+useTransform+useSpring), spring-entrance recipe cards, whileTap/whileHover micro-interactions,AnimatePresenceloading screen. - Neo-brutalist design system: Space Grotesk + IBM Plex Mono, hard offset shadows, high-contrast accent colors.
- Light gamification: drinks-invented counter with CountUp animation, menu streak tracker, achievement toasts.
AI / Infrastructure
- Groq API (llama-3.3-70b-versatile, JSON mode) for low-latency structured generation.
- Render free tier (backend) + Vercel (frontend) + cron-job.org keep-alive ping every 10 minutes.
Challenges we ran into
- The gap crosshair disappeared at the worst moment: the chart hid the target point exactly when the generated drink was shown. Fixed by always rendering it (dimmed post-generation rather than removed).
- Batch naming convergence: early prompts produced four "Brown Sugar X" variants. Solved with a diversity constraint ("at most one shared ingredient per pair with prior batch drinks") and a richer demo preset with 10 varied ingredients.
- Render's 30-second request timeout: sequential batch generation took ~90s and
hit the timeout, showing a cryptic network error. Fixed by parallelizing all 4 Groq
calls with
asyncio.gather. - Fraction parsing in cost arithmetic:
"1/4 tsp"parsed as1.0because the decimal regex matched"1"first. Fixed by putting the fraction alternative before the decimal alternative in the compiled regex. - Groq daily token limits: 100k TPD is generous but finite during heavy live testing. Rate limit errors now return a clean 429 with a human-readable retry time instead of a silent crash.
Accomplishments that we're proud of
- Three independently verifiable computations on every single screen: the gap point you can verify by hand against the chart, the cost arithmetic you can check with a calculator, and the constraint compliance warnings that catch exactly what the model got wrong.
- The parallax landing page: four depth layers of genuine scroll physics, not a CSS trick. Feature cards rotate in from ±3° and spring to zero on scroll entry.
- The batch mode: greedy farthest-point sampling means the four drinks are mathematically guaranteed to be spread across flavor space, not clustered.
- Zero "vibes" in the pipeline: Every number on screen is something you can verify by hand.
What we learned
- Deterministic pre-computation as a constraint dramatically improves LLM output quality: when you tell the model "target sweetness 3.2, acidity 7.8" it produces far more novel drinks than "invent something interesting."
- Closing the verification loop in plain Python (not a second LLM call) is the only honest way to claim constraint compliance.
- Render's free tier is genuinely usable for hackathon demos if you parallelize your LLM calls and keep the server alive with a cron ping.
What's next for Palette
- Seasonal ingredient calendars: automatically flag which gap targets are achievable with what's in season this week.
- Customer feedback loop: scan POS data to weight the flavor-space search toward what actually sells, not just what's geometrically novel.
- Supplier integration: surface the cheapest source for the ingredients needed to fill the computed gap before the next order is placed.
- Multi-location: aggregate gap data across a café chain so a flagship location's innovation gets propagated to branches with different ingredient lists.


Log in or sign up for Devpost to join the conversation.