Privacy-first cosmetic ingredient analyzer. Users photograph an INCI label and get an AI-generated risk score, comedogenic rating and irritation potential per ingredient — plus cross-product compatibility analysis and a drag-and-drop weekly skincare routine. User data (history, calendar, profile) lives only on the device; the server keeps just anonymous cache + rate-limit metadata.

Cosmetic ingredient lists (INCI) are opaque: most consumers cannot interpret the names, nor detect conflicts between products used simultaneously. Skinwise closes that gap with a multimodal AI pipeline that stays cheap, fast and privacy-respecting.
Users upload 1–4 photos of a product label — multi-photo support handles cylindrical packaging where the full list wraps around. The server normalizes images via sharp to ≤1024px JPEG, computes a SHA-256 hash, looks it up in Upstash Redis (24-hour TTL), and on cache miss calls Gemini 2.5 Flash-Lite (multimodal vision) with a structured Turkish-language prompt. The API returns a typed ProductAnalysis object with per-ingredient breakdowns and a composite risk score, then writes the result back to Redis so a re-upload of the same image hits the cache for free.
/icerik-analizi — single-product label analysis/cakisma-analizi — accepts 2–3 products, surfaces RoutineConflict records (ingredient pairs that should not be used together)/rutin — drag-and-drop weekly skincare routine; old /takvim URLs permanent-redirect here. The backend endpoint stays /api/routine.User-facing data (analysis history, calendar entries, profile) lives only on the device — localStorage with a 30-entry rolling cap, no account, no upload. The server only persists two anonymous artifacts in Upstash Redis: an IP hash for rate-limit counters, and an image-hash → ProductAnalysis cache (24-hour TTL, no user identity attached).
A storage.ts migration layer auto-splits legacy multi-day routine entries into per-day records, so each day has its own independent completion checkmark. Analyses can be exported as PNG report cards via a Canvas-based renderer (lib/export-report.ts).
Browser → FormData → /api/analyze
↓
sharp normalize (≤1024px JPEG)
↓
SHA-256 image hash
↓
Upstash Redis lookup (24h TTL)
↓ (miss)
Gemini 2.5 Flash-Lite (multimodal vision)
↓
JSON parse + normalize → typed ProductAnalysis
↓
Redis cache write
↓
Frontend: AnalysisContext + localStorage history (max 30)
RATE_LIMIT_ENABLED)prefers-color-schemeAnimatePresence transitions across UploadZone, AddEntryModal and the calendar@upstash/redis, @upstash/ratelimit)