STYLE is an AI-guided fragrance discovery application. A user supplies one to five visual references, reviews an editable interpretation built from a controlled 154-label visual vocabulary, adds practical scent preferences, and receives five differentiated fragrances from the bundled 500-record catalogue. Structured feedback after physical testing deterministically changes the next selection.
STYLE never lets a language model choose or invent a fragrance. AI performs one multimodal visual translation, can regenerate the controlled Style Card from an explicit short user instruction, and may polish the prose for all five already-ranked fragrances in one small text request. Catalogue selection, signal derivation, fallbacks and learning remain inspectable TypeScript functions over local sourced data.
- React 19, strict TypeScript, Vite and React Router power a mobile-first single-page interface.
- Zod validates Style Profile v0.2, controlled labels, fragrance signals, preferences, feedback and local profile migrations.
- A deterministic local engine follows controlled visual label → editorial fragrance signal → catalogue evidence, then applies five role-specific selectors with diversity penalties.
- Anonymous state lives behind a persistence interface backed by
localStorage; image bytes are excluded. - Cloudflare Pages Functions expose
/api/style/analyze,/api/style/refine,/api/recommendations/copyand/api/health. - Production analysis uses a Workers AI binding; local development defaults to a deterministic mock provider.
- Vitest, Testing Library and Playwright cover domain rules, accessible UI states and the essential journey.
The visual system follows the supplied Etheric Brutalism references: dramatic Bodoni display type, Syne body text, Space Mono metadata, hard borders, asymmetric overlap, and restrained atmospheric cyan/pink fields.
Requirements: Node.js 22+ and npm 10+.
npm install
copy .env.example .env.local
npm run devOpen http://localhost:5173. VITE_AI_PROVIDER=mock enables the complete offline journey. The mock result is deliberately labelled and does not claim to inspect pixels.
| Variable | Runtime | Purpose |
|---|---|---|
VITE_AI_PROVIDER |
browser build | mock for Vite-only local development or cloudflare to call the Pages Function |
AI_PROVIDER |
Pages Function | mock or cloudflare; production default is cloudflare |
AI_MODEL |
Pages Function | Workers AI model, default @cf/google/gemma-4-26b-a4b-it |
No secret token belongs in any VITE_* variable. Workers AI production access uses the AI binding.
Workers AI calls may consume quota. After authenticating Wrangler and accepting the model licence, build and run:
npm run build
npx wrangler pages dev dist --ai AISet VITE_AI_PROVIDER=cloudflare before the build and keep AI_PROVIDER=cloudflare. If the binding, quota or model is unavailable, the UI offers a manual Style Card route.
npm run validate:data
npm run typecheck
npm run lint
npm run test
npm run build
npx playwright install chromium
npm run test:e2enpm run quality runs catalogue validation, type checking, lint, unit/component tests and the production build. Playwright is separate because its browser binary is an environment-level install.
The app imports data/fragrance_top500_v0.2.json directly and never mutates it. scripts/validate-data.mjs checks each of the 500 records against data/fragrance_record_schema_v0.2.json, verifies the declared count and rejects duplicate IDs before every production build.
To replace or extend the catalogue:
- Keep each record at schema version
0.2, or add an explicit migration and update the runtime types. - Preserve source IDs, confidence fields, warnings and nulls.
- Update
dataset_metadata.record_count. - Run
npm run validate:dataand the recommendation tests. - Review every new user-facing fact for supporting source data.
The app never shows price or current availability from this dataset because no record contains the required current verification. Neutral typographic placeholders are used instead of fabricated bottle images.
The browser validates JPEG/PNG/WebP files, rejects originals over 8 MB, resizes the longest side to 1024px, re-renders through canvas to strip common metadata and encodes WebP. Prepared blobs exist only in memory and the analysis request; object-URL previews may remain temporarily while the user reviews the Style Card, then are revoked. Source images are not stored in localStorage, a database, object storage, analytics or application logs.
Only the resulting Style Profile, the latest short Style Card refinement instruction, scent preferences and testing history are stored in the current browser. A refinement request is text-only and never resends reference images. The profile page can delete all stored data. Analysis and refinement prompts explicitly prohibit demographic, sensitive, attractiveness, health and psychological inference.
The free-hosting path is Cloudflare Pages with Pages Functions and a Workers AI binding:
npm ci
npm run quality
npm run build
npx wrangler login
npx wrangler pages project create style-fragrance-discovery
npx wrangler pages deploy dist --project-name style-fragrance-discoveryIn the Pages project, add a Workers AI binding named AI and variables AI_PROVIDER=cloudflare and AI_MODEL=@cf/google/gemma-4-26b-a4b-it, then redeploy. Static routes are excluded from Function invocation by public/_routes.json. Verify the deployed /api/health and full analysis journey before considering the release complete. See deployment-cloudflare.md for the exact checklist.
- Product and design brief
- Architecture
- Data model
- Visual-style taxonomy
- Fragrance-signal ontology
- Visual-to-fragrance mapping
- Recommendation method
- LLM provider
- AI request budget
- Privacy
- Implementation plan
- Known limitations
The catalogue has no currently verified prices, availability or official-brand sources. Wearing context and performance are sparse and therefore usually neutral in scoring. Local profiles do not sync. The mock provider is a deterministic fixture, while production analysis requires a configured Cloudflare account, accepted model licence and available Workers AI quota. See known-limitations.md for the full list.