Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

34 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

OneDress Icon

OneDress πŸ’

Six bridesmaids, six skin tones, one dress color β€” provably no one's worst option.

OneDress Readme Hero



Live Demo Demo Video Pitch Deck Devpost


Next.js React TypeScript Tailwind Zod Vitest YouCam API CI Release Tests Coverage License


πŸ’‘ The Problem & Solution

Bridesmaid dresses are one color for the whole party β€” usually final-sale β€” and the color that flatters the bride can drain every other complexion in the photos. The color is chosen today by eyeballing one retailer model who matches nobody in the party.

OneDress measures each bridesmaid's real skin hex and Fitzpatrick depth, then solves a constrained group-optimization problem: find the single dress color where the least-flattered person is lifted the most (max-of-minimum fairness), and render it photorealistically on every bridesmaid at once.

It's the inverse of a personal-color quiz: not "what's my season?" but "given one garment all N people must wear, which single color harms the group least?" β€” a question single-user analysis can't answer, because the group's answer is never any individual's top pick.

πŸ”¬ Verified live against the YouCam API (Perfect Corp)

The sponsor SDK is the engine, not decoration. All five load-bearing endpoints are proven end-to-end by a live spike (scripts/spike.ts, npm run spike):

# Endpoint Role Verified result
1 skin-tone-analysis measured skin hex β€” the scoring input e.g. #bb9982 β†’ ITAΒ° 43, hueΒ° 61
2 fitzpatrick-scale-analyzer Type I–VI depth cross-check e.g. Type II
3 face-attr-analysis faceShape β†’ earring silhouette e.g. Heart
4 cloth-v3 render the winning color on each bridesmaid render-fidelity Ξ”E00 median 7.8 (5.5–11.2)
5 2d-vto/earring chained onto the render (undertone β†’ metal) gold hoop landed on the render

Measured cost: a full one-person run is 43 units (skin-tone 20 + fitzpatrick 10 + face-attr 10 + cloth-v3 2 + earring 1) β€” independently confirmed by the credit-balance delta in npm run bench. Analysis is the cost center, so each bridesmaid is measured once per run and every re-score after that is free (ranking all 24 colorways is pure local math, zero API cost).

🎨 The scoring engine (published, not a black box)

For each (bridesmaid p, colorway c):

flatter(p, c) = 0.50Β·U + 0.30Β·C + 0.20Β·S
  U  undertone harmony   β€” dress warmth vs skin warmth (from CIELAB hue angle)
  C  value contrast      β€” |Ξ”L*| in a flattering band (triangular, not "max distance")
  S  saturation harmony  β€” enough chroma separation to read as its own color

The group objective is Rawlsian max-of-minimum β€” optimize the worst individual, not the average:

groupScore(c) = min over bridesmaids p of flatter(p, c)
winner        = argmax over colorways c of groupScore(c)     ← nobody is anyone's worst
by-eye pick   = argmax over colorways c of mean_p flatter    ← how it's chosen today

The by-eye (mean-maximizing) pick is the counterfactual, and the gap between the two objectives is the whole product. Run on synthetic Fitzpatrick I–VI profiles, they choose different colorways: maximizing the average picks rust, which is excellent for four of six and drops the deepest-skin bridesmaid to 38.8/100. The maximin winner, marigold, lifts her to 65.3 β€” a 26.5-point swing on one person β€” with nobody in the party below 57.8.

Who the objective protects is party-dependent, not structural: it defends whoever the available palette serves worst, which on some parties is a mid-tone member nobody thinks to check. We report who it actually turned out to be on each run rather than assuming. The color math (sRGB β†’ CIELAB D65, ITAΒ°, hue angle, Ξ”E2000) is fixed physics; the three weights and transfer-function constants are disclosed, calibratable parameters. Full derivation: lib/colorway/engine.ts.

πŸ—οΈ Architecture & Tech Stack

graph TD
    A["πŸ“Έ 2 photos per bridesmaid<br/>face selfie + full-length"] --> B["Next.js API routes<br/>key stays server-side"]

    subgraph SKIN["πŸ”¬ YouCam Β· Skin AI β€” 3 endpoints"]
        C1["skin-tone-analysis<br/>β†’ measured hex"]
        C2["fitzpatrick-scale-analyzer<br/>β†’ Type I–VI"]
        C3["face-attr-analysis<br/>β†’ faceShape"]
    end

    B --> C1
    B --> C2
    B --> C3

    subgraph ENGINE["🎨 Our math β€” zero API cost"]
        D["CIELAB pipeline<br/>ITAΒ° Β· hue angle Β· Ξ”E2000"]
        E["Maximin scoring<br/>24 colorways Γ— N bridesmaids"]
        F["Winner + by-eye counterfactual"]
    end

    C1 --> D
    C2 --> D
    D --> E
    E --> F

    subgraph VTO["πŸ‘— YouCam Β· Apparel + Jewelry VTO β€” 2 endpoints"]
        G1["cloth-v3<br/>β†’ winner rendered on each bridesmaid"]
        G2["2d-vto/earring<br/>β†’ chained onto the render"]
    end

    F --> G1
    C3 --> G2
    G1 --> G2
    G2 --> H["πŸ† Verdict card<br/>lineup Β· guarantee Β· Ξ”E badge"]

    classDef io fill:#2B2430,stroke:#6B5A66,stroke-width:1px,color:#FAF5F7
    classDef skin fill:#D98BA3,stroke:#A85F7A,stroke-width:1px,color:#241520
    classDef math fill:#9CAF88,stroke:#6F8060,stroke-width:1px,color:#1B2116
    classDef vto fill:#B79BD4,stroke:#8468A8,stroke-width:1px,color:#1E1728
    classDef verdict fill:#E9C46A,stroke:#B08D3C,stroke-width:2px,color:#2A2210

    class A,B io
    class C1,C2,C3 skin
    class D,E,F math
    class G1,G2 vto
    class H verdict

    style SKIN fill:#D98BA31A,stroke:#A85F7A,stroke-dasharray:4 3
    style ENGINE fill:#9CAF881A,stroke:#6F8060,stroke-dasharray:4 3
    style VTO fill:#B79BD41A,stroke:#8468A8,stroke-dasharray:4 3
Loading

Rose is the sponsor's Skin AI, violet is its VTO, sage is our own math. The colour split is the Topic C argument in one glance: five YouCam endpoints doing the measuring and the rendering, with a deterministic engine in between that turns measurements into a decision.

Each bridesmaid is measured once per run; every re-score after that is pure local maths, so ranking all 24 colorways again costs zero API units. Only a re-render calls out.

Layer Technology
Frontend Next.js 15 (App Router), React 19, Tailwind CSS
Language TypeScript (strict)
API client Typed, Zod-validated wrapper (lib/youcam/) β€” token-bucket rate limit, retry/backoff, bounded polling. The API key stays server-side.
Scoring Pure, deterministic engine (lib/colorway/, lib/color/) β€” zero network, unit-tested
Sponsor API YouCam API (Perfect Corp) β€” 5 endpoints across Skin AI + Apparel VTO
Testing Vitest (unit) + Playwright (E2E) + Lighthouse CI

πŸ“Š Engineering rigor

Every number below is printed by a command in this repo β€” none are estimates. Where a number isn't measured yet, it says so instead of guessing. The live-API rows need a key; tests and coverage run with no key and no network.

Metric Value Where it comes from
Unit tests 77 passing npm run test
Coverage 100% β€” statements, branches, functions, lines npm run test:coverage
E2E tests 16 passing (desktop + mobile) npm run e2e
CI pipeline 6 stages, parallel, concurrency-guarded .github/workflows/ci.yml
Render fidelity Ξ”E00 median 7.8, range 5.5–11.2 (n=1 subject, 9 patches) npm run bench --yes
API cost 43 units per bridesmaid, measured npm run spike
Live endpoints proven 5 / 5 green npm run spike
End-to-end latency 33.4s for one subject; per-stage medians in DEMO.md npm run bench --yes
HTTP requests 47 for one subject (5 uploads Β· 5 PUTs Β· 5 creates Β· 26 polls Β· 4 credit Β· 2 downloads) npm run bench --yes
True p50 / p95 not measured β€” needs ~20 runs (β‰ˆ860 units, more than the grant) stated, not guessed

πŸ† Sponsor Track

Topic C β€” Skin AI + Apparel VTO combined. OneDress fuses three Skin AI endpoints (skin-tone-analysis, fitzpatrick-scale-analyzer, face-attr-analysis) with two Apparel/Jewelry VTO endpoints (cloth-v3, 2d-vto/earring) into a single decision β€” the measured skin values drive the render, they aren't shown side by side. Remove any one and the flow visibly breaks.

Why it could only be built on YouCam. The whole thesis needs a measured skin value, not a self-reported season or a swatch a user taps: skin-tone-analysis returns an actual hex we can push through CIELAB, and fitzpatrick-scale-analyzer gives an independent depth reading to cross-check it. That is the input the optimizer needs and the reason the output is defensible. Then the same vendor renders the result on the real person β€” so the color we solved for and the color you see on her are the same pipeline, and we can measure the drift (Ξ”E) between them.

Honest limitations. Three, stated here rather than left to be discovered:

1. The render drifts from the intended hex. cloth-v3 is a generative try-on, so the rendered fabric does not land exactly on the target. Measured across 9 patches of one render: median Ξ”E00 7.8, ranging 5.5 to 11.2. The median is a modest shift; the top of that range is not β€” at Ξ”E00 11 the fabric reads as a neighbouring colour. We publish the range rather than the flattering point estimate, and put the reference swatch beside every render. The decision is made on measured skin values and fixed colour math, not on the render; the render is how you check the decision.

2. The 24 colorway references are recoloured derivatives of one garment. Openly-licensed stock has almost no plain single-garment product shots outside a handful of common colours, so all 24 references in public/refs/colorways/ are one CC-licensed hanging dress remapped in Lab space to each target hex (full provenance in docs/asset-licences.md). Holding the garment constant is arguably the right control for a colour reference set — only the colour varies — but it does mean the ΔE00 of 0.51 between target and reference is calibration, not luck. It proves the swatches aren't mislabelled and that the Lab→sRGB→JPEG round trip didn't drift them. It is not evidence that a photograph happened to match.

3. The fairness objective only bites when a party spans widely. On a party reaching Fitzpatrick VI, maximin and mean choose different colorways and the gap is 26.5 points. On a narrower real party we measured (ITA 46.6 β†’ βˆ’13.3), both objectives chose the same colour and the counterfactual lift was zero. That is the honest behaviour: when no colorway disadvantages anyone, OneDress says so instead of manufacturing a difference.

πŸ›οΈ Why a retailer wants this

The group is the unit of purchase. A bridal party is roughly six dresses at ~$150 β€” a ~$900 basket that closes or collapses on a single decision. It's a final-sale category, so returns aren't the retailer's lever; the losses are abandoned carts and stalled group chats, which is exactly what an unresolvable colour argument produces.

Perfect Corp already sells try-on widgets onto this precise product page for 800+ brand partners β€” and every one of them answers "how does this look on me?". There is no group primitive in the category. OneDress is a net-new SKU for that shelf rather than a competitor to anything the sponsor ships: one embed that turns six undecided shoppers into one locked order, and differentiates the retailer on inclusivity in a category where getting skin tone wrong is permanently photographed.

One honest boundary: the widely-cited ~24–30% apparel return-rate lever does not apply here, because bridalwear is final-sale. That lever belongs to the returnable-apparel extension, not to the flow we built.

πŸš€ Getting Started

Prerequisites

  • Node.js β‰₯ 20
  • A YouCam API key (see .env.example)

Installation

git clone https://github.com/edycutjong/onedress.git
cd onedress
npm install
cp .env.example .env.local   # add your YOUCAM_API_KEY
npm run dev                  # http://localhost:3000

Routes β€” one deploy, three surfaces:

Route What it is Where it lives
/ Landing page public/landing.html (static, rewritten from /)
/pitch 10-slide pitch deck public/pitch.html (static, rewritten from /pitch)
/party The product β€” the 7-step flow app/party/page.tsx
/api/* Party orchestration, scoring, upload, credit app/api/**

Prove the SDK integration yourself

npm run spike     # runs all 5 endpoints live and prints a green summary + unit cost

The spike uses local throwaway fixtures (gitignored). No login, no accounts β€” the app is zero-config by design.

πŸ§ͺ Testing & CI

6-stage pipeline: Quality β†’ Security β†’ Build β†’ E2E β†’ Performance β†’ Deploy

npm run ci            # format:check + lint + typecheck + tests w/ coverage
npm run test          # Vitest unit tests
npm run e2e           # Playwright (mobile + desktop, zero-config)
npm run lighthouse    # Lighthouse CI (a11y is a hard gate)
make security-scan    # npm audit + license check
Layer Tool Status
Code Quality ESLint + Prettier + TypeScript strict βœ…
Unit Testing Vitest (77 tests, 100% coverage on all four metrics) βœ…
E2E Testing Playwright (54 specs: app, marketing routes, demo-mode, responsive) βœ…
Security (SAST) CodeQL βœ…
Security (SCA) Dependabot + npm audit βœ…
Secret Scanning TruffleHog βœ…
Performance Lighthouse CI βœ…

πŸ“ Project Structure

onedress/
β”œβ”€β”€ app/party/         # Next.js App Router β€” the product, the 7-step flow
β”œβ”€β”€ app/api/           # route handlers (party, score, upload, credit)
β”œβ”€β”€ public/            # landing.html + pitch.html (served at / and /pitch) + assets
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ youcam/        # typed API client (the only place calls are made)
β”‚   β”œβ”€β”€ color/         # CIELAB pipeline: ITAΒ°, hue angle, Ξ”E2000
β”‚   β”œβ”€β”€ colorway/      # 24 swatches + the maximin scoring engine
β”‚   └── earring/       # faceShape + undertone β†’ silhouette + metal
β”œβ”€β”€ __tests__/         # 77 unit tests, 100% coverage
β”œβ”€β”€ e2e/               # Playwright specs
β”œβ”€β”€ bench/             # reproducible benchmark (dry run by default, zero cost)
β”œβ”€β”€ scripts/spike.ts   # live 5-endpoint proof
β”œβ”€β”€ docs/              # README assets
└── .github/           # CI/CD, CodeQL, Dependabot, community health

πŸ“½οΈ Demo

Live: onedress.edycu.dev β€” landing Β· /party the app Β· /pitch the deck Video: posted at submission

Deployment is automated β€” see docs/deploy.md.

πŸ—ΊοΈ Roadmap

Pre-submission (deadline 2026-08-17).

  • Typed YouCam client β€” file β†’ task β†’ poll, rate limit, retry/backoff
  • Live 5-endpoint proof (npm run spike) β€” all green, cost measured
  • CIELAB pipeline + published maximin scoring engine, 77 unit tests at 100% coverage
  • Full harness: 6-stage CI, CodeQL, Dependabot, Playwright, Lighthouse
  • 7-step interactive UI (Create Β· Measure Β· Score Β· Compare Β· Render Β· Finish Β· Verdict)
  • Cached zero-unit demo party β€” the live URL's default, no key required
  • npm run bench β€” call counts, per-stage latency and Ξ”E distribution, published in DEMO.md
  • Post-hackathon: real garment catalogue integration, shareable party links

πŸ“„ License

MIT Β© 2026 Edy Cu

πŸ™ Acknowledgments

Built for the YouCam API Skin AI & Apparel VTO Hackathon. Thank you to Perfect Corp for the YouCam API.

About

πŸ’ Six bridesmaids, six skin tones, one dress color β€” measured skin hex + Fitzpatrick depth pick the colorway that provably flatters the whole party. Built on the Perfect Corp YouCam API.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages