// FRD Step 3 — rule-based regional nose-style recommendation. Renders the // /api/recommend payload as ranked flashcards, each with a free warp-preview // thumbnail (the enhanced variant) and score chips. const ScoreChip = ({ label, value, tone }) => (
{label} {value}
); const _compatTone = (c) => (c >= 70 ? "good" : c >= 50 ? "amber" : "red"); const _harmonyText = (h) => { if (!h || h.value == null) return "—"; if (h.delta == null) return `${h.value}`; const sign = h.delta >= 0 ? "+" : ""; return `${h.value} (${sign}${h.delta})`; }; const StyleFlashcard = ({ ctx, style, rank }) => { const { state, set } = ctx; const [previewUrl, setPreviewUrl] = React.useState(null); const enhanced = style.variants.enhanced; React.useEffect(() => { let alive = true; api.warp(state.session.session_id, enhanced.warp) .then((r) => { if (alive) setPreviewUrl(r.image_url); }) .catch(() => {}); return () => { alive = false; }; }, [style.key]); return (
{style.most_natural_match &&
Most Natural Match
}
#{rank}
{previewUrl ? {style.name} :
warp preview…
}

{style.name}

Default targets (aspirational)
{style.default_metrics.map((m, i) => {m})}
{style.approximation &&
≈ {style.approximation}
}
); }; const RecommendStep = ({ ctx }) => { const { state, set } = ctx; const [loading, setLoading] = React.useState(false); const [error, setError] = React.useState(null); React.useEffect(() => { if (!state.session || !state.region || !state.gender) return; if (state.recommendation) return; setLoading(true); api.recommend(state.session.session_id, state.region, state.gender) .then((rec) => { set({ recommendation: rec, intensityProfile: rec.profile }); setError(null); }) .catch((e) => setError(e.message)) .finally(() => setLoading(false)); }, [state.session && state.session.session_id, state.region, state.gender, state.recommendation]); if (!state.session) { return (

Add a portrait first

Go to step 1 to pick a region, biological gender and photo.

); } if (!state.region || !state.gender) { return (

Region & gender needed

Step 1 needs a region and biological gender before styles can be recommended.

); } const rec = state.recommendation; return (
Recommended styles · {state.region} · {state.gender}
{rec && intensity · {rec.profile}}
{loading &&
Scoring styles against your analysis…
} {error &&
{error}
} {rec && (
{rec.styles.map((s, i) => )}
)}
); }; Object.assign(window, { ScoreChip, StyleFlashcard, RecommendStep });