Inspiration

Foundation is bought on a swatch on a screen, then returned, or it sits unused on a shelf next to a bottle that already matched. The cost is not the tube, it is the wrong buy, and an opened foundation cannot be resold, so a wrong buy is waste for the shopper and a loss for the retailer. ShadeVeto checks one foundation before purchase and makes shade risk visible instead of forcing a confident answer.

What it does

The check is a decision engine, not a shade finder, and it runs in this order:

  1. Owned-bottle entry first. If a bottle the shopper already owns already matches, the receipt says NO, do not buy, before any paid API step runs. No mixing recommendation is ever made.
  2. Capture gate. The face photo must pass a framing gate (face more than 60% of frame width, measured by the browser detector, with a plain refusal where no detector exists), then a server-side re-check. No measurement is fabricated where the detector cannot measure.
  3. One Skin Tone reading, ranked against a licensed catalog of 617 foundation shades (The Pudding foundation shades dataset, 2018 swatches, MIT license, brand names stripped, hex-code labels only) by CIEDE2000 colour distance.
  4. Cluster rule. BUY fires only when the winner is inside the 6.0 match range and every shade within the noise floor of the winner (3.0 standard, 5.0 pale) is also inside it. The receipt names the whole cluster. If any cluster member falls outside the range, the verdict is SHADE-RISK. On the licensed catalog the distribution is BUY 3 of 6 study tones, SHADE-RISK 3 of 6. Those three SHADE-RISK tones are the tones whose cluster straddles the boundary; "gap 3 of 6" in the earlier draft meant exactly that, not a third bucket, and the phrase is dropped here to avoid the contradictory reading.
  5. Receipt. Every receipt is BUY, SHADE-RISK, NO, or CAN'T TELL, and every number in the reason traces to a measurement, with a fixed template fallback where a number is missing instead of invented precision.
  6. Preview and skipped-step audit. On a live BUY receipt, a makeup try-on (VTO) render of the winning shade runs as a labelled preview: 1 extra unit, produced after the verdict, and it never feeds the verdict; the receipt caption says so. Fixture receipts do not run it. A Skin Analysis step is NOT run in the shipped flow; it is logged as skipped on every receipt because its scores carry no verdict authority. Camera Kit is NOT built.

All study faces are synthetic, generated in ChatGPT, by a deliberate consent decision. No real faces are used anywhere in the product, demo, or study. Results are therefore a controlled, privacy-safe, reproducible benchmark on six synthetic tones, not a real-shopper accuracy rate.

Dual mode, honestly stated: the app is live at shadeveto.vercel.app, and live mode and fixture mode share the same decision path. Live mode runs behind a judge passcode: shadeveto-judge-2026 (enter it on the check page, or open https://shadeveto.vercel.app/check?passcode=shadeveto-judge-2026, which readies live mode without auto-running). Server-side spend caps apply (60 units per session, 300 global reserve), with exactly-once task creation (never auto-retried, because a slow server plus a retry can create two tasks and charge twice) and resume by task id. Public visitors without the passcode get the fixture replay only and spend 0 units; the fixture catalog is BUY 6 of 6 and is labelled fixture, never presented as live results. The demo video shows a complete live run on the hosted app: balance 702 to 681, exactly 21 units (20 for the Skin Tone reading, 1 for the VTO preview), verdict BUY.

How it was built

Next.js and TypeScript, hosted on Vercel, with the YouCam API client built as two-step upload, task creation plus polling, and typed results. Two YouCam endpoints run in the shipped flow: Skin Tone decides the verdict, and Makeup VTO renders a labelled preview on live BUY receipts, after the verdict, never feeding it. The rigor around those calls is the product: upload/polling state machine, no auto-retry idempotency, session and global spend caps, CIEDE2000 ranking, cluster rule, and a skipped-step audit log. Test suite: 266 checks green, typecheck clean, production build green.

Challenges

Two cuts define the product. First, the jawline measurement plan was dropped: measuring a VTO blend at the jawline measured the renderer, not the physical foundation, so VTO was cut from verdict authority entirely. It survives only as a labelled preview on live BUY receipts, rendered after the verdict and never feeding it. Second, the Camera Kit capture slice stopped at review: Perfect Corp's documented Camera Kit surface exposes no numeric face-share or bounding box, only qualitative face states, so feeding the capture gate from it would have fabricated a measurement. The gate stays on the browser detector.

What we learned

Dense catalogs break winner-take-all shade matching: under the old margin rule BUY was unreachable, 0 of 6, because top-two spacing of 0.25 to 1.10 sits inside the 3.0 and 5.0 noise floors, so the cluster rule, not a threshold tweak, was the fix. API cost control has to be idempotency by design, not a retry policy added later: one non-idempotent task creation plus one auto-retry is a double charge. And a single successful API call is not a product: the submission claims only what the live runs, the tests, and the six-tone benchmark actually prove.

Built With

Share this project:

Updates

Submission history