/* Studio 76 Atelier — THE DRESSING ROOM (virtual stylist). Your photo → a full-length virtual model (identity-locked) → trend chips or your own words → styled looks, ONE at a time. Talks only to dressing.php (keys server-side). */ const STYLES = [ 'Quiet luxury', 'Old money', 'Power tailoring', 'Soft minimal', 'Streetwear', 'Athleisure', 'Indo-western', 'Festive', 'After dark', 'Coastal linen', 'Denim on denim', 'Monochrome', 'Colour pop', 'Vintage revival', ]; const MKEY = 's76_dr_model'; function downscale(file, max) { return new Promise((res, rej) => { const fr = new FileReader(); fr.onerror = () => rej(new Error('Could not read that file.')); fr.onload = () => { const img = new Image(); img.onload = () => { const s = Math.min(1, max / Math.max(img.width, img.height)); const c = document.createElement('canvas'); c.width = Math.round(img.width * s); c.height = Math.round(img.height * s); c.getContext('2d').drawImage(img, 0, 0, c.width, c.height); res(c.toDataURL('image/jpeg', 0.85)); }; img.onerror = () => rej(new Error('That does not look like an image.')); img.src = fr.result; }; fr.readAsDataURL(file); }); } async function api(action, payload) { const r = await fetch('dressing.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(Object.assign({ action }, payload)), }); const j = await r.json().catch(() => ({})); if (!r.ok) throw new Error(j.error || ('Server error ' + r.status)); return j; } function LookCard({ look }) { if (!look) return null; return (

{look.name}

{look.palette ? Palette{look.palette} : null} {look.shoes ? Shoes{look.shoes} : null} {look.accents ? Accents{look.accents} : null}
{look.note ?

{look.note}

: null}
); } function DressingRoom() { const [stage, setStage] = React.useState('capture'); // capture -> model -> room const [photo, setPhoto] = React.useState(null); const [model, setModel] = React.useState(null); // {id, dataUrl} const [styles, setStyles] = React.useState([]); const [custom, setCustom] = React.useState(''); const [closet, setCloset] = React.useState(''); const [look, setLook] = React.useState(null); // {look, dataUrl} const [seen, setSeen] = React.useState([]); const [tweak, setTweak] = React.useState(''); const [busy, setBusy] = React.useState(''); const [err, setErr] = React.useState(''); const [saved, setSaved] = React.useState(null); const fileRef = React.useRef(null); const camRef = React.useRef(null); React.useEffect(() => { try { const m = JSON.parse(localStorage.getItem(MKEY) || 'null'); if (m && m.dataUrl) { setModel(m); setStage('model'); } } catch (e) {} }, []); const fail = (e) => { setErr(e.message || String(e)); setBusy(''); }; async function onFile(file) { if (!file) return; setErr(''); try { setPhoto(await downscale(file, 1280)); } catch (e) { fail(e); } } async function makeModel() { if (!photo) return; setBusy('Sketching your model — 20 seconds or so…'); setErr(''); try { const r = await api('model', { photo }); const m = { id: r.id, dataUrl: r.dataUrl }; setModel(m); setStage('model'); setBusy(''); try { localStorage.setItem(MKEY, JSON.stringify(m)); } catch (e) {} } catch (e) { fail(e); } } async function dressMe(surprise) { if (!model) return; setBusy(surprise ? 'The stylist is choosing for you…' : 'Styling your look…'); setErr(''); try { const r = await api('outfit', { model_id: model.id, model: model.dataUrl, styles, custom, closet, avoid: seen, surprise: !!surprise, }); setLook(r); setStage('room'); setBusy(''); setTweak(''); setSaved(null); if (r.look && r.look.name) setSeen((s) => [...s.slice(-7), r.look.name]); } catch (e) { fail(e); } } async function doTweak() { if (!look || !tweak.trim()) return; setBusy('Adjusting…'); setErr(''); try { const r = await api('refine', { image: look.dataUrl, instructions: tweak.trim(), aspect: '3:4' }); setLook((l) => ({ ...l, dataUrl: r.dataUrl })); setBusy(''); setTweak(''); } catch (e) { fail(e); } } async function saveLook() { if (!look) return; setBusy('Saving to your lookbook…'); setErr(''); try { const s = await api('save_start', { spec: { garment_type: (look.look && look.look.name) || 'Styled look' } }); await api('save_item', { setid: s.setid, key: 'look', label: (look.look && look.look.name) || 'The look', dataUrl: look.dataUrl }); await api('save_item', { setid: s.setid, key: 'model', label: 'The model', dataUrl: model.dataUrl }); setSaved(s.url); setBusy(''); } catch (e) { fail(e); } } function retake() { setModel(null); setPhoto(null); setLook(null); setSeen([]); setStage('capture'); try { localStorage.removeItem(MKEY); } catch (e) {} } const chip = (s) => ( ); return (
Studio 76
The Atelier Apparel bench Try it on

The Dressing Room

Your stylist, who has actually met you.

One photo becomes your virtual model. Then the room dresses you: one considered look at a time, never a lineup.

{err ?
{err}
: null} {busy ?
{busy}
: null} {stage === 'capture' && (
fileRef.current && fileRef.current.click()} onDragOver={(e) => e.preventDefault()} onDrop={(e) => { e.preventDefault(); onFile(e.dataTransfer.files && e.dataTransfer.files[0]); }}> {photo ? Your photo :

Drop your photo here

or tap to choose · we keep nothing

}
onFile(e.target.files[0])} /> onFile(e.target.files[0])} />
)} {stage === 'model' && model && (
Your virtual model
Your model · ready to be styled

The style rail

{STYLES.map(chip)}

In your words optional

setCustom(e.target.value)} placeholder="date night but comfortable · a wedding in Jaipur · first day at a new job" />

Your closet optional