Skip to content
Return to Projects
Case Study

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

Next.js 15TypeScriptPostgreSQLpgvectorClaude AIDocker

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

Edge
Cloudflare Tunnel
Application
Next.js 15 SSR
Subscriptions
Gamification
Intelligence
Claude AI
Flavor Profiler
Data
PostgreSQL
pgvector (semantic search)
Runtime
Docker

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.

  1. 1Move embedding generation to a background queue — synchronous calls during admin product writes briefly blocked the UI.
  2. 2Add a staging container that mirrors prod data shape so AI tuning doesn't happen against the live DB.
  3. 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

UseSizeWeightColor
Page title28–48px700--ice-10
Section h220–28px600--ice-9
Card title16px500--ice-9
Body copy14px400--ice-7
Caption/meta11–12px400--ice-6
Eyebrow9–10px400--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 →

~ End of Document ~