);
}
function TryOn() {
const [selfie, setSelfie] = React.useState(null); // dataUrl
const [piece, setPiece] = React.useState(null); // dataUrl OR absolute url
const [spec, setSpec] = React.useState(null);
const [place, setPlace] = React.useState('neck');
const [adv, setAdv] = React.useState(null); // null | 'loading' | {undertone, metals, stones, note}
const [out, setOut] = React.useState('idle'); // idle | loading | dataUrl
const [cap, setCap] = React.useState(null); // null | 'loading' | {caption, hashtags}
const [copied, setCopied] = React.useState(false);
const [pieceUrlText, setPieceUrlText] = React.useState('');
const [err, setErr] = React.useState('');
// Arrive from a saved set (?set=), a direct image (?img=), or a piece handed off from the Stylist
React.useEffect(() => {
let handed = null;
try { handed = sessionStorage.getItem('s76_piece'); if (handed) sessionStorage.removeItem('s76_piece'); } catch (e) {}
if (handed) {
setPiece(handed); setSpec(null);
api('analyze', { image: handed }).then(a => { setSpec(a.spec); setPlace(placeFor(a.spec && a.spec.piece_type)); }).catch(() => {});
return;
}
const q = new URLSearchParams(location.search);
const setId = (q.get('set') || '').replace(/[^a-f0-9]/g, '');
const img = q.get('img');
if (/^[a-f0-9]{20}$/.test(setId)) {
const base = 'sets/' + setId + '/';
fetch(base + 'index.json').then(r => r.json()).then(m => {
const cover = m.original ? base + m.original : (m.items && m.items[0] ? base + m.items[0].file : '');
if (cover) setPiece(absUrl(cover));
if (m.spec) { setSpec(m.spec); setPlace(placeFor(m.spec.piece_type)); }
}).catch(() => {});
} else if (img) {
bringPiece(img);
}
}, []);
async function onSelfie(file) {
if (!file || !/^image\//.test(file.type)) { setErr('Please choose an image file.'); return; }
setErr(''); setAdv('loading');
try {
const d = await downscale(file, 1280);
setSelfie(d);
try { const a = await api('skintone', { selfie: d }); setAdv(a); } catch (e) { setAdv(null); }
} catch (e) { setErr('Could not read that photo.'); setAdv(null); }
}
async function onPieceFile(file) {
if (!file || !/^image\//.test(file.type)) { setErr('Please choose an image file.'); return; }
setErr('');
try {
const d = await downscale(file, 1280);
setPiece(d); setSpec(null);
try { const a = await api('analyze', { image: d }); setSpec(a.spec); setPlace(placeFor(a.spec && a.spec.piece_type)); } catch (e) {}
} catch (e) { setErr('Could not read that image.'); }
}
async function bringPiece(url) {
url = (url || '').trim();
if (!/^https?:\/\//i.test(url)) { setErr('Paste a valid image URL.'); return; }
setErr(''); setPiece(url); setSpec(null); setPieceUrlText('');
try { const a = await api('analyze', { imageUrl: url }); setSpec(a.spec); setPlace(placeFor(a.spec && a.spec.piece_type)); } catch (e) {}
}
function tryOn() {
if (!selfie || !piece || out === 'loading') return;
setErr(''); setOut('loading'); setCap(null); setCopied(false);
const payload = { selfie, place, spec: spec || {} };
if (isData(piece)) payload.piece = piece; else payload.pieceUrl = absUrl(piece);
api('tryon', payload)
.then(j => setOut(j.dataUrl))
.catch(e => { setOut('idle'); setErr(e.message || 'Try-on failed — try a clearer, front-facing photo.'); });
}
function loadCaption() {
setCap('loading'); setCopied(false);
api('caption', { spec: spec || {} }).then(j => setCap(j)).catch(e => { setCap(null); setErr(e.message || 'Could not write a caption.'); });
}
function capFull(c) { return (c.caption || '') + '\n\n' + (c.hashtags || []).map(h => '#' + String(h).replace(/^#/, '').replace(/\s+/g, '')).join(' '); }
function copyCap(c) { const t = capFull(c); const done = () => { setCopied(true); setTimeout(() => setCopied(false), 1800); };
try { navigator.clipboard.writeText(t).then(done, done); } catch (e) { done(); } }
const ready = selfie && piece;
return (
Studio 76BenchImaginariumStylist✦ Try It On
See it on you.
Add a clear, front-facing photo and the piece you love — we place the real piece on you,
true to its actual size, and read a palette made for your skin.
{err &&
{err}
}
{/* left: inputs */}
1 · Your photo
{selfie
?
: }
{adv === 'loading' &&
Reading your palette…
}
{adv && adv !== 'loading' && (
Your palette · {adv.undertone} undertone
Metals that flatter you: {(adv.metals || []).map(m => {m})}
Stones to love: {(adv.stones || []).map(s => {s})}