/* Studio 76 — The Stylist. Upload an outfit → AI styles the jewellery that completes it. Add your own instructions, render the ones you love, then see them on you. Talks only to blueprint.php (style → style_render). */ async function api(action, payload) { const r = await fetch('blueprint.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(Object.assign({ action }, payload)) }); let j = {}; try { j = await r.json(); } catch (e) {} if (!r.ok || j.error) throw new Error(j.error || ('Something went wrong (' + r.status + ')')); return j; } function downscale(file, max) { return new Promise((res, rej) => { const fr = new FileReader(); fr.onerror = () => rej(new Error('read')); fr.onload = () => { const img = new Image(); img.onload = () => { const s = Math.min(1, max / Math.max(img.width, img.height)); const w = Math.round(img.width * s), h = Math.round(img.height * s); const c = document.createElement('canvas'); c.width = w; c.height = h; c.getContext('2d').drawImage(img, 0, 0, w, h); res(c.toDataURL('image/jpeg', 0.86)); }; img.onerror = () => rej(new Error('img')); img.src = fr.result; }; fr.readAsDataURL(file); }); } const isUrl = x => typeof x === 'string' && x.slice(0, 5) === 'data:'; function Stylist() { const [stage, setStage] = React.useState('idle'); // idle | analyzing | ready const [src, setSrc] = React.useState(null); const [id, setId] = React.useState(null); const [outfit, setOutfit] = React.useState(null); const [picks, setPicks] = React.useState([]); const [renders, setRenders] = React.useState({}); // index → dataUrl | 'loading' const [instr, setInstr] = React.useState(''); const [urlText, setUrlText] = React.useState(''); const [drag, setDrag] = React.useState(false); const [modal, setModal] = React.useState(''); const [err, setErr] = React.useState(''); const fileRef = React.useRef(null); async function handleFile(file) { if (!file || !/^image\//.test(file.type)) { setErr('Please choose an image file.'); return; } setErr(''); setStage('analyzing'); setOutfit(null); setPicks([]); setRenders({}); setId(null); try { const d = await downscale(file, 1280); setSrc(d); const j = await api('style', { image: d, instructions: instr }); setId(j.id); setOutfit(j.outfit); setPicks(j.picks || []); setStage('ready'); } catch (e) { setErr(e.message || 'Could not read that outfit.'); setStage('idle'); } } async function handleUrl(url) { url = (url || '').trim(); if (!/^https?:\/\//i.test(url)) { setErr('Paste a valid image URL.'); return; } setErr(''); setStage('analyzing'); setOutfit(null); setPicks([]); setRenders({}); setId(null); setSrc(url); try { const j = await api('style', { imageUrl: url, instructions: instr }); setId(j.id); setOutfit(j.outfit); setPicks(j.picks || []); setStage('ready'); } catch (e) { setErr(e.message || 'Could not bring that in.'); setStage('idle'); } } function renderPick(i) { if (!id || renders[i] === 'loading') return; setRenders(r => Object.assign({}, r, { [i]: 'loading' })); api('style_render', { id, pick: picks[i], instructions: instr }) .then(j => setRenders(r => Object.assign({}, r, { [i]: j.dataUrl }))) .catch(e => { setRenders(r => { const n = Object.assign({}, r); delete n[i]; return n; }); setErr(e.message || 'Render failed.'); }); } function renderAll() { picks.forEach((p, i) => { if (!renders[i]) renderPick(i); }); } function tryOn(dataUrl) { try { sessionStorage.setItem('s76_piece', dataUrl); } catch (e) {} window.location.href = '/tryiton.html'; } function reset() { setStage('idle'); setSrc(null); setId(null); setOutfit(null); setPicks([]); setRenders({}); setErr(''); } const Row = ({ l, v }) => v ?
{l}{v}
: null; return (
Studio 76 Bench Imaginarium Try-on ✦ The Stylist

Style the whole look.

Show us what you’re wearing — a dress, a saree, a whole outfit — and we’ll style the jewellery that completes it. Render the pieces you love, then see them on you.

{err &&

{err}

} {stage === 'idle' && (
fileRef.current && fileRef.current.click()} onDragOver={e => { e.preventDefault(); setDrag(true); }} onDragLeave={() => setDrag(false)} onDrop={e => { e.preventDefault(); setDrag(false); handleFile(e.dataTransfer.files[0]); }}>

Drop your outfit photo, or browse

A clear, well-lit shot of the look — on you or laid out — works best.

handleFile(e.target.files[0])} />
setInstr(e.target.value)} />

Optional — your instructions guide what we recommend & how it’s rendered.

setUrlText(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') handleUrl(urlText); }} />
)} {stage === 'analyzing' && (
Styling your look…
)} {stage === 'ready' && (
Styled for your look {picks.length > 0 && }
{picks.map((p, i) => { const r = renders[i]; return (
{p.type} {isUrl(r) ? {p.name} setModal(r)} /> : r === 'loading' ?
Rendering…
:
}
{p.name} {[p.style, p.metal, p.stones].filter(Boolean).join(' · ')} {p.why && “{p.why}”} {isUrl(r) && (
Download
)}
); })}
)}
setModal('')}> × {modal && e.stopPropagation()} />}
); } ReactDOM.createRoot(document.getElementById('root')).render();