Inspiration

Most "AI try-on" demos answer one question - what would I look like in this dress? and stop. We wanted a single selfie to answer two: what does my future look like, and what does my next big moment look like? Skincare is a 10-year story you're either telling intentionally or by accident; styling is a 60-second decision before walking out the door. Glow Forecast collapses both into a draggable, shareable artifact.

What it does

One selfie -> two acts: Act 1 - Future You. HD Skin Analysis, Skin Tone, Aging Generator, and Skin Simulation run in parallel. The result is a draggable split-screen: your face in 10 years, neglected on the left, cared for on the right. A six-product AM/PM ritual is generated automatically from your top three concerns. Act 2 - Tonight You. Pick a moment (Date Night, Job Interview, Wedding, Vacation, Casual Friday). Hair Transfer, Makeup VTO, Cloth-v3, and Bag/Jewelry/Shoes VTO build a coordinated look on the same selfie. A shoppable cart lets you tap any piece to add it to your basket in one move. Everything bundles into a story-sized Glow Card for sharing.

How I built it

Frontend: React + TypeScript + Vite + Tailwind, Zustand for session state, framer-motion for the layered reveal, html-to-image for the share card export. Proxy: Deno + Hono. Holds the YouCam API key server-side and forwards three endpoint families (/file/:feature, /task/:feature, /task/:feature/:id). Per-feature version pinning so each YouCam slug routes to the right v1.0 / v2.0 path. Pipeline shape: every Perfect Corp call follows the same four-step dance - request a presigned upload URL, PUT the bytes directly to S3, create a task, and poll until task_status === "success". We wrote a single runFeature helper, so adding a new model is one line.

A minimal example of the pipeline:

const result = await runFeature(
  "makeup-vto",
  selfieBlob,
  "selfie.jpg",
  (file_id) => ({
    src_file_id: file_id,
    effects: [{
      category: "lip_color",
      shape: { name: "original" },
      style: { type: "full" },
      palettes: [{ color: "#a85f4a", texture: "matte", colorIntensity: 60 }],
    }],
    version: "1.0",
  }),
);

Demo Mode: a parallel fixture path that returns deterministic results when the network is hostile - the demo never breaks during judging.

Challenges I ran into

Every API had a different body schema, and the docs only told us so much. We learned the shapes the hard way, one validation error at a time:

  • aging rejected our envelope-style { request_id, payload: { file_sets, actions } } body. Real shape: flat { src_file_id }. The API auto-generates an age series; we pick the frames closest to currentAge + 5 and +10 from the response.
  • hair-transfer failed with missing required properties: ref_file_id. There's no template catalog: it's a transfer-from-photo feature. We had to upload a second reference image alongside the selfie, then call the task with both file IDs.
  • makeup-vto rejected { look_id } because there is no look catalog: the body is an effects array with per-category schemas. lip_color requires shape + style at the effect level, with palettes like { color, texture, colorIntensity, gloss }. Valid shape.name values are an enum (original, plump, petal, pouty, etc.), and "natural" is silently invalid (passes schema, fails at runtime).
  • clothes-vto 404'd at the proxy: the real upstream slug is cloth-v3, not clothes-vto. The body needs garment_category and a reference garment image (ref_file_id).

CORS-tainted canvas on the share card. Perfect Corp's S3 result URLs don't return permissive CORS headers, so html-to-image choked when snapshotting the card. Fix: pre-fetch every <img> in the card and inline them as data URLs before the canvas snapshot runs.

async function urlToDataUrl(url: string): Promise<string> {
  const res = await fetch(url, { mode: "cors" });
  const blob = await res.blob();
  return await new Promise<string>((resolve) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result as string);
    reader.readAsDataURL(blob);
  });
}

Skin Analysis ships its results inside a presigned ZIP. We download the archive client-side, parse the JSON manifest, and resolve mask PNGs by name - no proxy round-trip needed.

Resilience over completeness. With finite API credits, any failure that bricked the demo was unacceptable. Each Act 2 stage is wrapped so that a missing reference image or an unmappable schema gracefully falls back to a fixture, and the user still reaches the Lookbook with whatever stages succeeded.

Accomplishments that I'm proud of

  1. 8 Perfect Corp APIs from one selfie, glued by one reusable runFeature helper.
  2. Reverse-engineered four undocumented schemas (aging, hair-transfer, makeup-vto, cloth-v3) by treating every 400 as a spec.
  3. A demo that refuses to break - every stage falls back to a fixture, so judges always reach the Lookbook.
  4. CORS-safe share-card export via inline data URLs, beating the tainted-canvas problem that kills most HTML-to-image integrations.
  5. Client-side ZIP extraction for HD Skin Analysis - no extra proxy round-trip.
  6. A narrative arc, not a feature dump - Future You + Tonight You, an AM/PM ritual, a shoppable cart, and a story-sized Glow Card all tied to one selfie.

What I learned

Read every error like it's a schema dump. Perfect Corp's "missing required properties" errors are essentially a free spec - once we treated each 400 as documentation, the iteration loop got very fast. anyOf schemas are a discoverability tax. A single error response listed every union branch the validator tried. Painful to read, but it told us exactly which fields each category needed. Assume reference-image, not catalog ID. Half of YouCam's "transfer" features (hair, cloth-v3, earring) take an uploaded reference image rather than a string ID - a different mental model than look_id / garment_id. Once we internalized this, three features collapsed into one upload-twice + run pattern. The demo card is the product. Whatever ends up shareable is what people remember. We spent disproportionate time on the Glow Card - typography, layering, CORS-safe export - because that's the artifact that escapes the demo.

What's next for Glow Forecast App

  • Real catalog integration for accessory VTO (bag, ring, shoes) once we wire up the per-feature reference-image flow.
  • Tone-aware product filtering - the API already returns undertone; we can route warm/cool/neutral palettes to different SKUs.
  • Gender-aware default looks so the makeup category swaps from lip_color to skin_smooth on masculine selfies, no manual override needed.
  • A short looping video of the neglected -> cared-for transition for sharing instead of a static split.

Built With

Share this project:

Updates

Submission history