Brewhaus
An AI-driven specialty coffee marketplace for home enthusiasts.
The Problem
Home coffee enthusiasts struggle to find beans matched to their taste. Brewhaus profiles flavor with AI and recommends roasts that fit each palate — turning discovery into an interactive experience.
My Role
Built solo with AI-assisted development — SSR storefront, vector search, AI integration, and Dockerized deployment.
Highlights
- Next.js 15 SSR storefront with gamification and subscription billing
- pgvector semantic search powering AI-driven flavor profiling
- Claude AI integration for personalized recommendations
- Containerized with Docker and served via Cloudflare Tunnels
Stack
Constraints
- Self-hosted on a home server — no cloud bill, but also no auto-scaling.
- Solo build, so the stack had to favour boring, well-documented pieces over novelty.
- AI cost must stay bounded — recommendations had to use embeddings, not a per-request LLM call.
System Architecture
Key Trade-offs
The decisions worth defending — what I chose, what I turned down, and why.
Recommendation engine
Chose
pgvector similarity on cached embeddings
Rejected
LLM call per recommendation
Vector search is millisecond-fast and effectively free once embeddings are computed; LLMs would have added cost and latency per page view.
Public ingress
Chose
Cloudflare Tunnel
Rejected
Public VPS with open ports
No exposed origin IP, free TLS, and DDoS protection inherited from Cloudflare — much smaller attack surface for a self-hosted box.
Rendering model
Chose
Next.js 15 SSR
Rejected
Pure client-rendered SPA
Product pages need to be indexable and shareable; SSR gives fast first paint and clean OG cards without a separate prerender step.
What I'd Do Differently
An honest retrospective — the stuff I'd change with more time, more users, or a second pass.
- 1Move embedding generation to a background queue — synchronous calls during admin product writes briefly blocked the UI.
- 2Add a staging container that mirrors prod data shape so AI tuning doesn't happen against the live DB.
- 3Track recommendation quality with a click-through metric instead of relying on subjective spot-checks.
Technical Deep-Dive
Architecture, specifications, and implementation details.
09 · Frontend Design System — Ice Cave
#Design Philosophy
Brewhaus uses an "Ice Cave" aesthetic — the antithesis of warm coffee shop clichés. Deep glacial blues, crystalline surfaces, and fractured-ice motifs create a premium, otherworldly atmosphere that is completely unique in the Romanian coffee market.
#Color Tokens
:root {
/* Ice palette — 11 stops from cave-dark to frost-light */
--ice-0: #010A12; /* Cave deep — page background */
--ice-1: #031523; /* Nav, sidebar backgrounds */
--ice-2: #062340; /* Card backgrounds */
--ice-3: #0A3560; /* Interactive elements base */
--ice-4: #0E4A82; /* Hover states */
--ice-5: #1565A8; /* Borders, dividers */
--ice-6: #1E82CC; /* Secondary text, muted labels */
--ice-7: #4DB8E8; /* Body text on dark surfaces */
--ice-8: #85D6F5; /* Primary text */
--ice-9: #C0EDFC; /* Headings, high-emphasis text */
--ice-10: #EAF8FF; /* Near-white text */
/* Accent */
--cyan: #00E5FF; /* Primary CTA, active states, glow effects */
--teal: #00BCD4; /* Secondary accent */
/* Semantic (mapped from ice palette) */
--surface-page: var(--ice-0);
--surface-nav: var(--ice-1);
--surface-card: rgba(6, 35, 64, 0.6);
--surface-glass: rgba(10, 53, 96, 0.3);
--border-default: rgba(77, 184, 232, 0.2);
--border-strong: rgba(77, 184, 232, 0.4);
--border-glow: rgba(0, 229, 255, 0.4);
}
#Typography
/* Headings — tech/sci-fi feel */
font-family: 'Orbitron', sans-serif;
/* Usage: Page titles, section headers, KPI numbers */
/* Body — clean, legible, modern */
font-family: 'Rajdhani', sans-serif;
/* Usage: Body copy, labels, nav links, descriptions */
/* Code / Data — monospace */
font-family: 'JetBrains Mono', monospace;
/* Usage: Prices, SKUs, technical specs, code blocks */
##Type Scale
| Use | Size | Weight | Color |
|---|---|---|---|
| Page title | 28–48px | 700 | --ice-10 |
| Section h2 | 20–28px | 600 | --ice-9 |
| Card title | 16px | 500 | --ice-9 |
| Body copy | 14px | 400 | --ice-7 |
| Caption/meta | 11–12px | 400 | --ice-6 |
| Eyebrow | 9–10px | 400 | --cyan |
#Component Library
##IceCard
The primary container component. Used for products, features, guides.
// Base variant
<div className="ice-card p-6">
{children}
</div>
// Highlighted variant (featured items)
<div className="ice-card ice-card-highlight p-6">
{children}
</div>
// CSS
.ice-card {
background: var(--surface-card);
border: 0.5px solid var(--border-default);
border-radius: 4px;
position: relative;
overflow: hidden;
transition: border-color 0.2s;
}
.ice-card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0; height: 1px;
background: linear-gradient(90deg, transparent, rgba(77,184,232,0.4), transparent);
}
.ice-card:hover { border-color: var(--border-strong); }
.ice-card-highlight { border-color: var(--border-glow); }
##StalactiteHeader
Fixed decorative SVG above every page — ice stalactites hang from the top.
// Position: fixed, top: 0, z-index: 0, pointer-events: none
// Opacity: 0.4
// Uses linearGradient from --ice-4 to transparent
// 13 stalactite polygons spanning 1080px viewBox
// 5 ice crack lines at random angles
##CyanButton (Primary CTA)
<button className="px-8 py-3 bg-cyan text-ice-0 font-mono text-xs
tracking-widest uppercase rounded-sm
hover:bg-ice-8 transition-colors font-bold">
Start Quiz →
</button>
##OutlineButton (Secondary)
<button className="px-8 py-3 border border-ice-5/40 text-ice-8
font-mono text-xs tracking-widest uppercase
rounded-sm hover:bg-ice-3 transition-colors">
Browse catalog
</button>
##IceBadge (Tags/Pills)
// Cyan variant — primary highlights
<span className="font-mono text-[9px] tracking-wider px-2 py-1
bg-cyan/10 border border-cyan/30 text-cyan rounded-sm">
Bestseller
</span>
// Blue variant — secondary tags
<span className="font-mono text-[9px] tracking-wider px-2 py-1
bg-ice-5/30 border border-ice-7/30 text-ice-8 rounded-sm">
Washed
</span>
##ProgressBar (XP / Level)
<div className="h-1 bg-ice-3 rounded-sm overflow-hidden">
<div
className="h-full bg-cyan rounded-sm transition-all duration-1000"
style={{ width: `${progress}%` }}
/>
</div>
#Visual Motifs
##Stalactites
Fixed SVG at top of every page. Polygon shapes descending from y=0 with a linear gradient fade to transparent. Adds cave depth without affecting content.
##Ice Crack Lines
Diagonal <line> elements at 0.5px, rgba(21,101,168,0.3). Used as
decorative section separators and on the cave ceiling SVG.
##Crystalline Card Borders
Every card has a 1px horizontal shimmer across the top edge via ::before
pseudo-element with linear-gradient(90deg, transparent, rgba(77,184,232,0.4), transparent).
##Cyan Glow
Active states, CTAs, and selected elements use text-shadow: 0 0 40px rgba(0,229,255,0.4).
Subtle — not neon. Icy, not electric.
##QUIC Pulse Animation
Animated dots and chevrons indicating live tunnel activity. Used on the tech spec section and the cloudflared status indicator:
@keyframes pulse-glow {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
#Page Backgrounds
All pages use the radial cave gradient:
background:
radial-gradient(ellipse 80% 60% at 20% 10%,
rgba(14,74,130,0.35) 0%, transparent 70%),
radial-gradient(ellipse 60% 80% at 80% 90%,
rgba(6,35,64,0.5) 0%, transparent 60%),
#010A12;
#Tailwind Extension
// tailwind.config.ts
colors: {
ice: {
0: "#010A12",
1: "#031523",
2: "#062340",
3: "#0A3560",
4: "#0E4A82",
5: "#1565A8",
6: "#1E82CC",
7: "#4DB8E8",
8: "#85D6F5",
9: "#C0EDFC",
10: "#EAF8FF",
},
cyan: "#00E5FF",
teal: "#00BCD4",
}
Next: 10 — Testing Strategy →