A menswear feed that learns your taste.

LUFF pulls 12,000+ pieces from 112 independent menswear labels into one feed, then personalizes it from every tap — an AI-tagged catalog, taste embeddings, and an occasion-first onboarding, built and shipped end-to-end.

↑ live — pick scenes, then ♥ / ✕ to train it

How it works

The hosted product ran on Flask + Supabase Postgres with a Python recommender; this demo distills the same experience into a single static page — the catalog, tags, and taste model all run client-side.

One catalog from 112 indie labels

A Shopify-aware scraper ingests and normalizes product data — titles, prices, sale state, imagery — with womenswear and non-clothing screened out at ingest.

AI-tagged, vision-first

Every product is vision-classified into a style taxonomy — community lanes, archetypes, garment categories, vacation relevance — and embedded with FashionCLIP for visual similarity and taste vectors.

Taste that compounds

Likes and passes train per-brand and per-tag affinities (server-side: a FashionCLIP taste vector). Ranking balances personalization against diversity caps so a loved brand tilts the feed without flooding it.

Occasion-first onboarding

Instead of a cold taste quiz, the front door asks what you're shopping for — Fire Island, circuit night, work — and seeds the feed from the picks. Iterated across three onboarding designs, measured at every step.

Built and measured like production

Every fix below shipped with a before/after measurement — throttled-network reproductions, real-user percentiles in PostHog, and a funnel that starts before the app bundle even loads.

295
automated tests (API + Playwright E2E), green in CI on every push
0.31 → 0.07
worst single layout shift while scrolling (CLS), after reserving image boxes
4.9s → 2.4s
first product photo on throttled cellular, after making hero delivery zero-round-trip
1.1s
real-user p90 time-to-interactive onboarding (PostHog, 30 days)
12,373
products in the live catalog, refreshed by pipeline
112
independent menswear brands, deduped and quality-gated

The experiment

LUFF ran as a real product experiment — customer validation interviews, repeated paid Instagram campaigns, and full-funnel analytics down to a pre-bundle boot beacon and a stall watchdog.

What the data said — and what I did about it

The demand signals were real: validation interviews kept surfacing the same pain — great clothes scattered across dozens of small-label sites — and the ads earned genuine clicks. The product held up too: 95% of ad clicks rendered in about a second, with zero stalls. What I couldn't crack was stickiness. I iterated the onboarding hard — a photo-grid taste quiz, a forced-choice "this or that" opener, an occasion-first picker, each with its own copy tests — and while every version moved first-session engagement, none of them turned interest into a habit across many ad tests.

The part I'm proudest of is the recommendation engine: a vision-tagged style taxonomy plus FashionCLIP embeddings that delivered genuinely good personalization from very few datapoints — a handful of taps visibly reshaped the feed. It passed the most honest test I had: it kept finding me clothes I loved and wouldn't have found otherwise. That engine is what's running in the demo above.

So I made the call: sunset the hosted product, keep what's durable — the catalog pipeline, the recommender, and this fully static demo ($0/month, nothing to break). What I'd do differently: run cheaper stickiness experiments with warm communities before paying for cold traffic.

Stack

Python · FlaskSupabase PostgresFashionCLIP embeddings LLM vision taggingPostHog analyticspytest + Playwright GitHub Actions CIRenderVanilla JS (this demo)