// Mella — Client Portal (mobile-first) · "the progress thread"
// Concept (grounded in how pet-portrait artists actually work): a private,
// chronological thread of updates. Reference photos the client gave sit at top;
// each stage (sketch -> underpainting -> final) lands as a dated entry with a
// short message; the ones that need it ask for approval. Works for 1 update or 6.
// Replaces "follow my Instagram to watch your commission." Built on mella-kit.jsx.

const { useState, useEffect } = React;
const { hexToRgba, ACCENTS, MONO, PALS, ART, resolveTheme, Painting, Field, inp, ta, BottomSheet, PhoneShell } = window;

// Normalizes a photo entry — either a raw palette array (gradient stand-in) or an
// {src,pos,pal} art object — into props for <Painting>. Lets one PhotoStack mix
// real finished paintings with gradient placeholders (sketches, client snaps).
const paintProps = (ph) => Array.isArray(ph) ? { pal: ph } : { pal: ph.pal, src: ph.src, pos: ph.pos };

const MONEY = { total: 260, deposit: 130, balance: 130 };
const ATTN = '#B05536', DONE = '#5C6536';

// Maple — a Saint Bernard memorial. Her finished portrait is the real painting,
// and her reference photo is matched to it. The progress thread is a milestone
// timeline (see MILESTONES); only the final delivery carries an image.
const MAPLE_ART = ART.stbHead;
const MAPLE_PHOTO = { pal: PALS.maple, src: 'art/stbernard-head-photo.jpg', pos: 'center 22%', ratio: 0.743 }; // client's reference snapshot, matched to the painting
const STG = {
  photo: MAPLE_PHOTO,
  final: { pal: MAPLE_ART.pal, src: MAPLE_ART.src, pos: MAPLE_ART.pos, ratio: MAPLE_ART.ratio }, // the real painting
};

// Reference photos the client provided. One real snapshot for now; more slot in here.
const REF_PHOTOS = [MAPLE_PHOTO];

// The journey as a milestone timeline. Intermediate steps are text-only markers
// (artists rarely photograph every stage), so they need no image; only the final
// delivery carries the painting + the approval flow.
const MILESTONES = [
  { stage:'Rough sketch',   date:'Jun 22' },
  { stage:'Refined sketch', date:'Jun 25' },
  { stage:'Underpainting',  date:'Jul 2'  },
  { stage:'Detail pass',    date:'Jul 6'  },
];
const FINAL = {
  id:'final', stage:'Final', date:'Jul 9',
  msg:"Okay, she's done. I sat with this one a good long while, I really wanted to do her justice, and I think she's finally there. Take your time and have a proper look. If she feels like your Maple, just approve and I'll let her dry for a week or so before she ships out to you. No rush at all, she's not going anywhere until you're happy.",
};

/* ================================================================== page = */
function ClientPortal({ t }) {
  const c = resolveTheme(t);
  // stage tweak: progress (final not done) · review (final posted) · balance · delivered
  const stage = t.stage || 'review';
  const initialPhase = stage === 'balance' ? 'balance' : (stage === 'delivered' ? 'paid' : 'awaiting');
  const [phase, setPhase] = useState(initialPhase);   // awaiting -> balance -> paid
  const [sheet, setSheet] = useState(null);           // {type, update} | null
  const [approvedDir, setApprovedDir] = useState({}); // update id -> true (direction approved)
  const [notes, setNotes] = useState({});             // update id -> client's note text
  const [depositPaid, setDepositPaid] = useState(false);
  useEffect(() => { setPhase(initialPhase); setApprovedDir({}); setNotes({}); setDepositPaid(false); }, [t.stage]);

  // Early states: the deposit-checkout landing (from the email) and the just-booked state.
  if (stage === 'deposit' && !depositPaid) return <DepositCheckout c={c} onPaid={() => setDepositPaid(true)} />;
  if (stage === 'confirmed' || (stage === 'deposit' && depositPaid)) return <Confirmed c={c} justPaid={depositPaid} />;

  // In 'progress' the painting (final) hasn't been posted yet; milestones still show.
  const finalPosted = stage !== 'progress';
  const stepCount = MILESTONES.length + (finalPosted ? 1 : 0);
  const finalApproved = phase !== 'awaiting';
  const shipped = stage === 'delivered';        // artist has marked it shipped
  const paid = phase === 'paid';
  // NOTE: after the balance is paid we deliberately do NOT auto-advance to a
  // "drying/curing" status. Whether there's a cure wait is artist-specific and
  // agreed in advance (future: an artist setting). The artist marks shipped.

  const eyebrow = { fontFamily: MONO, fontSize: 10, letterSpacing: '0.16em', textTransform: 'uppercase', color: c.faint, fontWeight: 500 };
  const head = (size, ex) => ({ fontFamily:c.f.head, fontWeight:c.f.hw, letterSpacing:c.f.kern, lineHeight:1.06, color:c.text, margin:0, fontSize:size, ...ex });
  const { wide, tier } = window.useViewport();
  const land = tier === 'desktop';   // iPad landscape / desktop: two-column reading layout
  const pad = (wide && !land) ? { padding:'0 20px', maxWidth:600, marginLeft:'auto', marginRight:'auto' } : { padding: '0 20px' };
  const headPad = land ? { padding:'0 24px', maxWidth:1060, marginLeft:'auto', marginRight:'auto' } : pad;
  const colsWrap = land ? { display:'flex', gap:0, maxWidth:1060, margin:'0 auto', alignItems:'flex-start' } : {};
  const asideCol = land ? { width:336, flexShrink:0, alignSelf:'flex-start', position:'sticky', top:0, paddingRight:24, paddingTop:4, borderRight:`1px solid ${c.rule}` } : {};
  const mainCol = land ? { flex:1, minWidth:0, paddingLeft:24 } : {};

  const approveFinal = () => { setPhase('balance'); setSheet(null); };
  const approvePayNow = () => { setPhase('balance'); setSheet({ type:'pay' }); };
  const approveDirection = (id) => { setApprovedDir(p => ({ ...p, [id]:true })); };

  return (
    <div style={{ fontFamily: c.f.body, color: c.text, background: c.bg, position:'relative', minHeight:'100%' }}>

      {/* ---------- header ---------- */}
      <div style={{ ...headPad, paddingTop: 15, paddingBottom: 13, display:'flex', alignItems:'center', gap:11, borderBottom:`1px solid ${c.rule}` }}>
        <Painting pal={ART.leonberger.pal} src={ART.leonberger.src} pos={ART.leonberger.pos} radius={'50%'} style={{ width:32, height:32, flexShrink:0 }} />
        <div style={{ flex:1, minWidth:0 }}>
          <div style={{ fontWeight:600, fontSize:14.5, color:c.text, lineHeight:1.1 }}>Iris Sandoval</div>
          <div style={{ fontSize:11, color:c.muted }}>a private link, just for you</div>
        </div>
        {!land && (
          <button onClick={() => setSheet({ type:'message' })} style={{ display:'flex', alignItems:'center', gap:6, background:c.surface, border:`1px solid ${c.rule}`, borderRadius:99, padding:'6px 12px', cursor:'pointer', fontFamily:c.f.body, fontSize:12.5, fontWeight:600, color:c.text }}>
            <span style={{ fontSize:13, color:c.a }}>✉</span> Message
          </button>
        )}
      </div>

      <div style={colsWrap}>
      <div style={asideCol}>
      {/* ---------- commission summary ---------- */}
      <div style={{ ...pad, paddingTop: 18, paddingBottom: 16, borderBottom:`1px solid ${c.rule}` }}>
        <div style={{ ...eyebrow, color:c.a }}>Your commission</div>
        <h1 style={{ fontFamily:c.f.body, fontWeight:600, fontSize:26, letterSpacing:'-0.015em', lineHeight:1.1, color:c.text, margin:'6px 0 0' }}>Maple</h1>
        <p style={{ margin:'5px 0 0', fontSize:13.5, color:c.muted, lineHeight:1.45 }}>Memorial portrait of a Saint Bernard, 11 × 14, oil on canvas</p>
        <div style={{ display:'flex', gap:8, marginTop:13 }}>
          <Pill c={c} label="Deposit" value={`$${MONEY.deposit}`} state="paid" />
          <Pill c={c} label="Balance" value={`$${MONEY.balance}`} state={paid ? 'paid' : (phase==='balance' ? 'due' : 'on approval')} accent={phase==='balance'} />
          <Pill c={c} label={(shipped || paid) ? 'Status' : 'Est. ready'} value={shipped ? 'Shipped' : (paid ? 'Preparing' : 'mid-July')} />
        </div>
        {phase === 'balance' && (
          <button onClick={() => setSheet({ type:'pay' })} style={{ ...solid(c), width:'100%', marginTop:13, padding:'13px' }}>Pay your ${MONEY.balance} balance</button>
        )}
      </div>

      {/* ---------- reference photos the client gave ---------- */}
      <div style={{ ...pad, paddingTop: 16 }}>
        <div style={{ display:'flex', alignItems:'baseline', justifyContent:'space-between' }}>
          <div style={eyebrow}>Your reference photos</div>
          {REF_PHOTOS.length > 1 && <span onClick={() => setSheet({ type:'photos', update:{ stage:'Your reference photos', date:'', photos:REF_PHOTOS } })} style={{ fontFamily:MONO, fontSize:10, color:c.a, cursor:'pointer' }}>view all {REF_PHOTOS.length} →</span>}
        </div>
        <div onClick={() => setSheet({ type:'photos', update:{ stage:'Your reference photos', date:'', photos:REF_PHOTOS } })} style={{ display:'flex', gap:8, marginTop:10, cursor:'pointer' }}>
          {REF_PHOTOS.map((p,i) => <Painting key={i} {...paintProps(p)} radius={8} style={{ width:58, height:58, flexShrink:0 }} />)}
        </div>
      </div>

      {land && (
        <div style={{ ...pad, paddingTop: 22 }}>
          <div onClick={() => setSheet({ type:'message' })} style={{ background:c.surface, border:`1px solid ${c.rule}`, borderRadius:12, padding:'13px 14px', cursor:'pointer', display:'flex', alignItems:'center', gap:11 }}>
            <Painting pal={ART.leonberger.pal} src={ART.leonberger.src} pos={ART.leonberger.pos} radius={'50%'} style={{ width:34, height:34, flexShrink:0 }} />
            <div style={{ flex:1, minWidth:0 }}>
              <div style={{ fontSize:13, fontWeight:600, color:c.text }}>Message Iris</div>
              <div style={{ fontSize:11.5, color:c.muted, marginTop:1 }}>A question, or just to say hello.</div>
            </div>
            <span style={{ fontSize:13, color:c.a }}>✉</span>
          </div>
        </div>
      )}
      </div>
      <div style={mainCol}>

      {/* ---------- THE PROGRESS THREAD ---------- */}
      <div style={{ ...pad, paddingTop: 24, paddingBottom: 12 }}>
        <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between' }}>
          <span style={eyebrow}>Progress</span>
          <span style={{ fontFamily:MONO, fontSize:10, color:c.faint, display:'flex', alignItems:'center', gap:6 }}>
            <span style={{ width:5, height:5, borderRadius:'50%', background:c.a }} />{stepCount} {stepCount === 1 ? 'update' : 'updates'}
          </span>
        </div>
      </div>

      <div style={{ ...pad, paddingBottom: 20 }}>
        {/* milestone markers — text-only steps along the journey */}
        {MILESTONES.map((m) => (
          <div key={m.stage} style={{ display:'flex', gap:13 }}>
            <div style={{ display:'flex', flexDirection:'column', alignItems:'center', width:13, flexShrink:0 }}>
              <div style={{ width:11, height:11, borderRadius:'50%', marginTop:5, flexShrink:0, background:DONE, border:`2px solid ${DONE}` }} />
              <div style={{ flex:1, width:2, background:c.rule, marginTop:3 }} />
            </div>
            <div style={{ flex:1, minWidth:0, paddingBottom:16, display:'flex', alignItems:'baseline', gap:8 }}>
              <span style={{ fontWeight:600, fontSize:14.5, color:c.text }}>{m.stage}</span>
              <span style={{ fontFamily:MONO, fontSize:10, color:c.faint }}>{m.date}</span>
            </div>
          </div>
        ))}

        {/* final delivery — the only step with an image + the approval flow */}
        {!finalPosted ? (
          <div style={{ display:'flex', gap:13 }}>
            <div style={{ display:'flex', flexDirection:'column', alignItems:'center', width:13, flexShrink:0 }}>
              <div style={{ width:11, height:11, borderRadius:'50%', border:`2px dashed ${c.rule}`, marginTop:5 }} />
            </div>
            <div style={{ flex:1, padding:'13px 14px', border:`1.5px dashed ${c.rule}`, borderRadius:11, background:c.sunk }}>
              <div style={{ fontWeight:600, fontSize:14, color:c.text }}>Final</div>
              <div style={{ fontSize:13, color:c.soft, marginTop:2 }}>Iris is finishing your piece. You'll get an email the moment it lands here to approve.</div>
            </div>
          </div>
        ) : (
          <div style={{ display:'flex', gap:13 }}>
            <div style={{ display:'flex', flexDirection:'column', alignItems:'center', width:13, flexShrink:0 }}>
              <div style={{ width:11, height:11, borderRadius:'50%', marginTop:5, flexShrink:0,
                background: finalApproved ? DONE : c.bg, border:`2px solid ${finalApproved ? DONE : c.a}` }} />
            </div>
            <div style={{ flex:1, minWidth:0 }}>
              <div style={{ display:'flex', alignItems:'baseline', gap:8, marginBottom:9 }}>
                <span style={{ fontWeight:600, fontSize:15, color:c.text }}>{FINAL.stage}</span>
                <span style={{ fontFamily:MONO, fontSize:10, color:c.faint }}>{FINAL.date}</span>
                {finalApproved && <span style={{ marginLeft:'auto', fontFamily:MONO, fontSize:9.5, color:DONE }}>✓ approved</span>}
              </div>

              <PhotoStack c={c} photos={[STG.final]} onOpen={() => setSheet({ type:'photos', update:{ ...FINAL, photos:[STG.final] } })} big />

              <p style={{ margin:'11px 0 0', fontSize:13.5, lineHeight:1.5, color:c.soft }}>{FINAL.msg}</p>

              {notes[FINAL.id] && (
                <div style={{ marginTop:11, marginLeft:'auto', maxWidth:'85%' }}>
                  <div style={{ background:c.a, color:c.aOn, borderRadius:'12px 12px 3px 12px', padding:'9px 12px', fontSize:13, lineHeight:1.45 }}>{notes[FINAL.id]}</div>
                  <div style={{ fontFamily:MONO, fontSize:9, color:c.faint, marginTop:4, textAlign:'right' }}>Iris will reply here</div>
                </div>
              )}

              {!finalApproved && (
                <div style={{ marginTop:12, padding:'14px', background:c.aSoft, border:`1px solid ${hexToRgba(c.a,0.3)}`, borderRadius:11 }}>
                  <div style={{ fontWeight:600, fontSize:14, color:c.text }}>Ready to approve?</div>
                  <div style={{ fontSize:12, color:c.muted, marginTop:2, lineHeight:1.45 }}>Approving sends your balance of ${MONEY.balance}. No charge for small tweaks.</div>
                  <div style={{ display:'flex', gap:8, marginTop:11 }}>
                    <button onClick={() => setSheet({ type:'note', update:FINAL })} style={{ ...ghost(c), flex:1, padding:'11px' }}>{notes[FINAL.id] ? 'Add another note' : 'Send a note'}</button>
                    <button onClick={() => setSheet({ type:'approve' })} style={{ ...solid(c), flex:1, padding:'11px' }}>Approve the final</button>
                  </div>
                </div>
              )}
              {finalApproved && (
                <div style={{ marginTop:12, fontSize:13, color:DONE, display:'flex', alignItems:'center', gap:7 }}>
                  <span>✓</span> You approved the final{shipped ? ', shipped Jul 12' : (paid ? ', balance paid' : '')}.
                </div>
              )}
            </div>
          </div>
        )}
      </div>

      {/* ---------- footer ---------- */}
      <div style={{ ...pad, paddingTop: 8 }}>
        <div style={{ background:c.sunk, border:`1px solid ${c.rule}`, borderRadius:12, padding:'13px 15px', display:'flex', alignItems:'center', justifyContent:'space-between', gap:12 }}>
          <div>
            <div style={{ fontSize:13, color:c.text, fontWeight:600 }}>Email me about new photos</div>
            <div style={{ fontSize:11.5, color:c.muted, marginTop:1 }}>Just a heads-up. Everything lives here on the page.</div>
          </div>
          <Toggle c={c} on />
        </div>
      </div>

      <footer style={{ ...pad, paddingTop: 16, paddingBottom: 36, textAlign:'center' }}>
        <p style={{ fontSize:11, color:c.faint, lineHeight:1.6, margin:0 }}>
          No account, no password. This link is just for you.<br/>Painted by hand, never AI. <span style={{ textDecoration:'underline' }}>Commission terms</span>.
        </p>
        <div style={{ marginTop:12, fontFamily:"'Instrument Serif', serif", fontSize:15, color:c.faint }}>on mella<span style={{ color:c.a }}>.</span></div>
      </footer>
      </div>
      </div>

      {sheet?.type === 'photos' && <PhotoViewer c={c} update={sheet.update} onClose={() => setSheet(null)} />}
      {sheet?.type === 'note' && <NoteSheet c={c} update={sheet.update} onClose={() => setSheet(null)} onSend={(text) => { setNotes(p=>({ ...p, [sheet.update.id]:text })); setSheet(null); }} />}
      {sheet?.type === 'pay' && <PaySheet c={c} onClose={() => setSheet(null)} onPaid={() => { setPhase('paid'); setSheet(null); }} />}
      {sheet?.type === 'approve' && <ApproveSheet c={c} onClose={() => setSheet(null)} onPayNow={approvePayNow} onPayLater={approveFinal} />}
      {sheet?.type === 'message' && <MessageSheet c={c} onClose={() => setSheet(null)} />}
    </div>
  );
}

/* ---------- message Iris (letter-like, not chat) ---------- */
const SEED_MESSAGES = [
  { from:'iris',   date:'Jun 21', text:"So glad you've trusted me with Maple. I'll start her sketch this week and post it here the moment it's ready. Feel free to share anything about her that you'd like me to capture." },
  { from:'client', date:'Jun 21', text:"Thank you. She used to rest her chin on my knee every evening. That quiet, gentle look in her eyes is the thing I'd most love to see." },
  { from:'iris',   date:'Jun 22', text:"That's exactly the kind of detail that makes a portrait feel like her. I'll keep it close while I work." },
];
function MessageSheet({ c, onClose }) {
  const [msgs, setMsgs] = useState(SEED_MESSAGES);
  const [text, setText] = useState('');
  const send = () => { const t = text.trim(); if (!t) return; setMsgs(m => [...m, { from:'client', date:'just now', text:t }]); setText(''); };
  const { BottomSheet } = window;
  return (
    <BottomSheet c={c} onClose={onClose} maxHeight="90%">
      <div style={{ display:'flex', alignItems:'center', gap:11, paddingBottom:14, borderBottom:`1px solid ${c.rule}`, marginBottom:6 }}>
        <Painting pal={ART.leonberger.pal} src={ART.leonberger.src} pos={ART.leonberger.pos} radius={'50%'} style={{ width:36, height:36, flexShrink:0 }} />
        <div style={{ flex:1, minWidth:0 }}>
          <div style={{ fontSize:14.5, color:c.text, fontWeight:600 }}>Message Iris</div>
          <div style={{ fontSize:11.5, color:c.muted }}>She usually writes back within a day.</div>
        </div>
        <span onClick={onClose} style={{ cursor:'pointer', color:c.faint, fontSize:22 }}>×</span>
      </div>

      {/* letter-like thread, not chat bubbles */}
      <div style={{ display:'flex', flexDirection:'column', gap:18, padding:'14px 0 18px' }}>
        {msgs.map((m,i) => {
          const mine = m.from === 'client';
          return (
            <div key={i} style={{ borderLeft:`2px solid ${mine ? c.rule : c.a}`, paddingLeft:13 }}>
              <div style={{ display:'flex', alignItems:'baseline', gap:8, marginBottom:5 }}>
                <span style={{ fontSize:12.5, fontWeight:600, color: mine ? c.soft : c.text }}>{mine ? 'You' : 'Iris'}</span>
                <span style={{ fontFamily:MONO, fontSize:9.5, color:c.faint }}>{m.date}</span>
              </div>
              <p style={{ margin:0, fontSize:14, lineHeight:1.55, color: mine ? c.soft : c.text, fontFamily: c.f.body, fontStyle:'normal', fontWeight:400 }}>{m.text}</p>
            </div>
          );
        })}
      </div>

      {/* compose — letter, not chat input */}
      <div style={{ borderTop:`1px solid ${c.rule}`, paddingTop:14 }}>
        <textarea value={text} onChange={e=>setText(e.target.value)} rows={3} placeholder="Write to Iris…"
          style={{ ...ta(c), fontSize:14.5 }} />
        <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginTop:10 }}>
          <span style={{ fontSize:11, color:c.faint }}>No rush. She'll see it soon.</span>
          <button onClick={send} style={{ background:c.a, color:c.aOn, border:'none', borderRadius:10, padding:'11px 20px', fontFamily:c.f.body, fontSize:14, fontWeight:600, cursor:'pointer' }}>Send</button>
        </div>
      </div>
    </BottomSheet>
  );
}

/* ---------- shared header ---------- */
function PortalHeader({ c }) {
  const { wide } = window.useViewport();
  const pad = wide ? { padding:'0 20px', maxWidth:600, marginLeft:'auto', marginRight:'auto' } : { padding: '0 20px' };
  return (
    <div style={{ ...pad, paddingTop: 15, paddingBottom: 13, display:'flex', alignItems:'center', gap:11, borderBottom:`1px solid ${c.rule}` }}>
      <Painting pal={ART.leonberger.pal} src={ART.leonberger.src} pos={ART.leonberger.pos} radius={'50%'} style={{ width:32, height:32, flexShrink:0 }} />
      <div style={{ flex:1, minWidth:0 }}>
        <div style={{ fontWeight:600, fontSize:14.5, color:c.text, lineHeight:1.1 }}>Iris Sandoval</div>
        <div style={{ fontSize:11, color:c.muted }}>a private link, just for you</div>
      </div>
      <span style={{ fontFamily:MONO, fontSize:8.5, letterSpacing:'0.12em', textTransform:'uppercase', color:c.faint, border:`1px solid ${c.rule}`, borderRadius:99, padding:'4px 8px' }}>Private</span>
    </div>
  );
}

/* ---------- deposit checkout (landing from the email) ---------- */
function DepositCheckout({ c, onPaid }) {
  const [done, setDone] = useState(false);
  const [paying, setPaying] = useState(false);
  const { wide } = window.useViewport();
  const pad = wide ? { padding:'0 20px', maxWidth:600, marginLeft:'auto', marginRight:'auto' } : { padding: '0 20px' };
  const eyebrow = { fontFamily: MONO, fontSize: 10, letterSpacing: '0.16em', textTransform: 'uppercase', color: c.faint, fontWeight: 500 };
  const pay = () => { setPaying(true); setTimeout(() => { setPaying(false); setDone(true); }, 850); };

  return (
    <div style={{ fontFamily:c.f.body, color:c.text, background:c.bg, minHeight:'100%' }}>
      <PortalHeader c={c} />

      {/* the moment: Iris accepted */}
      <div style={{ ...pad, paddingTop: 22, textAlign:'center' }}>
        <div style={{ width:46, height:46, borderRadius:'50%', background:c.aSoft, color:c.a, display:'flex', alignItems:'center', justifyContent:'center', fontSize:22, margin:'0 auto 14px' }}>✓</div>
        <div style={{ ...eyebrow, color:c.a }}>Iris accepted your commission</div>
        <h1 style={{ fontFamily:c.f.head, fontWeight:c.f.hw, fontSize:30*c.f.big+4, letterSpacing:c.f.kern, color:c.text, margin:'8px 0 0', lineHeight:1.08 }}>Reserve your slot</h1>
        <p style={{ fontSize:14, color:c.muted, lineHeight:1.5, margin:'9px auto 0', maxWidth:'34ch' }}>A deposit secures your place in Iris's queue. The balance isn't due until you approve the finished piece.</p>
      </div>

      {/* the piece */}
      <div style={{ ...pad, paddingTop: 20 }}>
        <div style={{ display:'flex', gap:13, alignItems:'center', background:c.surface, border:`1px solid ${c.rule}`, borderRadius:13, padding:'13px 14px' }}>
          <Painting {...paintProps(STG.photo)} radius={9} style={{ width:54, height:54, flexShrink:0 }} />
          <div style={{ flex:1, minWidth:0 }}>
            <div style={{ fontWeight:600, fontSize:16, color:c.text }}>Maple</div>
            <div style={{ fontSize:12, color:c.muted, marginTop:2 }}>Memorial portrait of a Saint Bernard, 11 × 14, oil</div>
          </div>
        </div>
      </div>

      {/* the math */}
      <div style={{ ...pad, paddingTop: 16 }}>
        <div style={{ background:c.surface, border:`1px solid ${c.rule}`, borderRadius:13, padding:'15px 16px' }}>
          {[['Portrait total', `$${MONEY.total}`, false], ['Deposit due now (50%)', `$${MONEY.deposit}`, true], ['Balance, after you approve', `$${MONEY.balance}`, false]].map((r,i) => (
            <div key={i} style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', padding:'5px 0', fontSize: r[2]?14.5:13, color: r[2]?c.text:c.muted, fontWeight: r[2]?600:400 }}>
              <span>{r[0]}</span><span style={{ fontFamily:MONO }}>{r[1]}</span>
            </div>
          ))}
        </div>
      </div>

      <div style={{ ...pad, paddingTop: 10 }}>
        {/* === STRIPE INTEGRATION POINT: deposit charge ===
            onClick should create a deposit PaymentIntent / Checkout Session for
            MONEY.deposit on the artist's connected account, then confirm. On the
            webhook 'payment_intent.succeeded', flip the portal to the 'confirmed'
            stage. The faux pay->done->onPaid below simulates that. */}
        <button data-stripe-action="pay-deposit" onClick={done ? onPaid : pay} disabled={paying} style={{ ...solid(c), width:'100%', padding:'16px', fontSize:15.5, opacity: paying?0.7:1 }}>
          {done ? 'Continue →' : (paying ? 'Processing…' : `Pay your $${MONEY.deposit} deposit`)}
        </button>
        <div style={{ display:'flex', alignItems:'center', justifyContent:'center', gap:7, marginTop:13, fontFamily:MONO, fontSize:10.5, color:c.faint }}>
          <span style={{ fontSize:11 }}>🔒</span><span>You'll finish on Stripe's secure checkout. Iris is paid directly.</span>
        </div>
        <p style={{ textAlign:'center', fontSize:11, color:c.faint, lineHeight:1.5, margin:'14px 0 0' }}>
          By paying, you reserve your slot and agree to Iris's <span style={{ textDecoration:'underline' }}>commission terms</span>. Deposits hold your place in the queue.
        </p>
      </div>

      <div style={{ height: 40 }} />
    </div>
  );
}

/* ---------- just confirmed (booked, no updates yet) ---------- */
function Confirmed({ c, justPaid }) {
  const { wide } = window.useViewport();
  const pad = wide ? { padding:'0 20px', maxWidth:600, marginLeft:'auto', marginRight:'auto' } : { padding: '0 20px' };
  const eyebrow = { fontFamily: MONO, fontSize: 10, letterSpacing: '0.16em', textTransform: 'uppercase', color: c.faint, fontWeight: 500 };
  return (
    <div style={{ fontFamily:c.f.body, color:c.text, background:c.bg, minHeight:'100%' }}>
      <PortalHeader c={c} />

      {/* booked */}
      <div style={{ ...pad, paddingTop: 24, textAlign:'center' }}>
        <div style={{ width:50, height:50, borderRadius:'50%', background:c.aSoft, color:c.a, display:'flex', alignItems:'center', justifyContent:'center', fontSize:24, margin:'0 auto 15px' }}>✓</div>
        <div style={{ ...eyebrow, color:c.a }}>{justPaid ? 'Deposit received' : "You're booked"}</div>
        <h1 style={{ fontFamily:c.f.head, fontWeight:c.f.hw, fontSize:30*c.f.big+4, letterSpacing:c.f.kern, color:c.text, margin:'8px 0 0', lineHeight:1.08 }}>Maple is in the queue</h1>
        <p style={{ fontSize:14, color:c.muted, lineHeight:1.5, margin:'9px auto 0', maxWidth:'34ch' }}>Your slot is reserved. Iris will start soon, and every photo will land right here on this page.</p>
      </div>

      {/* where you are */}
      <div style={{ ...pad, paddingTop: 22 }}>
        <div style={{ display:'flex', gap:8 }}>
          <Pill c={c} label="Deposit" value={`$${MONEY.deposit}`} state="paid" />
          <Pill c={c} label="Balance" value={`$${MONEY.balance}`} state="on approval" />
          <Pill c={c} label="Est. ready" value="mid-July" />
        </div>
      </div>

      {/* what happens next — a preview of the thread to come */}
      <div style={{ ...pad, paddingTop: 24 }}>
        <span style={eyebrow}>What happens next</span>
        <div style={{ marginTop: 14 }}>
          {[
            ['Iris starts your sketch', 'You approve the pose before she commits to paint.', true],
            ['She paints, and shares progress', 'Photos land here at each stage. You can comment any time.', false],
            ['You approve the final', 'Only then is your balance due. Then it ships to your door.', false],
          ].map((r,i,arr) => (
            <div key={i} style={{ display:'flex', gap:13 }}>
              <div style={{ display:'flex', flexDirection:'column', alignItems:'center', width:13, flexShrink:0 }}>
                <div style={{ width:11, height:11, borderRadius:'50%', marginTop:4, flexShrink:0, background: r[2]?c.a:c.bg, border:`2px solid ${r[2]?c.a:c.rule}` }} />
                {i<arr.length-1 && <div style={{ flex:1, width:2, background:c.rule, marginTop:3 }} />}
              </div>
              <div style={{ flex:1, paddingBottom: i<arr.length-1?18:0 }}>
                <div style={{ fontWeight:600, fontSize:14.5, color:c.text }}>{r[0]}</div>
                <div style={{ fontSize:12.5, color:c.muted, marginTop:2, lineHeight:1.45 }}>{r[1]}</div>
              </div>
            </div>
          ))}
        </div>
      </div>

      {/* reference photos on file */}
      <div style={{ ...pad, paddingTop: 22 }}>
        <span style={eyebrow}>Your reference photos</span>
        <div style={{ display:'flex', gap:8, marginTop:10 }}>
          {[STG.photo].map((p,i) => <Painting key={i} {...paintProps(p)} radius={8} style={{ width:58, height:58, flexShrink:0 }} />)}
          <div style={{ width:58, height:58, borderRadius:8, border:`1px dashed ${c.rule}`, background:c.sunk, display:'flex', alignItems:'center', justifyContent:'center', color:c.a, fontSize:18, flexShrink:0, cursor:'pointer' }}>+</div>
        </div>
        <div style={{ fontSize:11.5, color:c.faint, marginTop:8 }}>Thought of another good photo? You can still add it.</div>
      </div>

      {/* notify */}
      <div style={{ ...pad, paddingTop: 22 }}>
        <div style={{ background:c.sunk, border:`1px solid ${c.rule}`, borderRadius:12, padding:'13px 15px', display:'flex', alignItems:'center', justifyContent:'space-between', gap:12 }}>
          <div>
            <div style={{ fontSize:13, color:c.text, fontWeight:600 }}>Email me about new photos</div>
            <div style={{ fontSize:11.5, color:c.muted, marginTop:1 }}>A note from Iris at each stage.</div>
          </div>
          <Toggle c={c} on />
        </div>
      </div>

      <footer style={{ ...pad, paddingTop: 16, paddingBottom: 36, textAlign:'center' }}>
        <p style={{ fontSize:11, color:c.faint, lineHeight:1.6, margin:0 }}>No account, no password. This link is just for you.<br/>Painted by hand, never AI. <span style={{ textDecoration:'underline' }}>Commission terms</span>.</p>
        <div style={{ marginTop:12, fontFamily:"'Instrument Serif', serif", fontSize:15, color:c.faint }}>on mella<span style={{ color:c.a }}>.</span></div>
      </footer>
    </div>
  );
}

/* ---------- photo stack (1, 2, or 3+) ---------- */
function PhotoStack({ c, photos, onOpen, big }) {
  const n = photos.length;
  // Content camp: a single image renders WHOLE at its true aspect ratio (no crop),
  // but capped to a comfortable max height and centered — so tall portraits don't
  // run away down the thread. Tap opens the full-size view.
  if (n === 1) {
    const r = (photos[0] && photos[0].ratio) || 0.78;
    const CAP = 440;   // max displayed height (px); width follows from the ratio
    return (
      <div onClick={onOpen} style={{ cursor:'pointer' }}>
        <Painting {...paintProps(photos[0])} radius={10}
          style={{ width:'100%', maxWidth: Math.round(CAP * r), aspectRatio: String(r), margin:'0 auto' }} />
      </div>
    );
  }
  return (
    <div onClick={onOpen} style={{ cursor:'pointer', display:'grid', gridTemplateColumns: n===2 ? '1fr 1fr' : '2fr 1fr', gap:6 }}>
      <Painting {...paintProps(photos[0])} radius={10} style={{ width:'100%', height: big ? 230 : 168, gridRow: n>2 ? 'span 2' : 'auto' }} />
      {n === 2 && <Painting {...paintProps(photos[1])} radius={10} style={{ width:'100%', height: big ? 230 : 168 }} />}
      {n > 2 && (
        <div style={{ display:'flex', flexDirection:'column', gap:6 }}>
          <Painting {...paintProps(photos[1])} radius={10} style={{ width:'100%', height: big ? 112 : 81 }} />
          <div style={{ position:'relative' }}>
            <Painting {...paintProps(photos[2])} radius={10} style={{ width:'100%', height: big ? 112 : 81 }} />
            {n > 3 && <div style={{ position:'absolute', inset:0, borderRadius:10, background:hexToRgba('#000',0.45), color:'#fff', display:'flex', alignItems:'center', justifyContent:'center', fontFamily:MONO, fontSize:15 }}>+{n-3}</div>}
          </div>
        </div>
      )}
    </div>
  );
}

/* ---------- small bits ---------- */
function Pill({ c, label, value, state, accent }) {
  const sc = state==='paid' ? DONE : (accent ? c.a : c.faint);
  return (
    <div style={{ flex:1, background:c.surface, border:`1px solid ${accent ? hexToRgba(c.a,0.4) : c.rule}`, borderRadius:10, padding:'9px 11px' }}>
      <div style={{ fontFamily:MONO, fontSize:8.5, letterSpacing:'0.08em', textTransform:'uppercase', color:c.faint }}>{label}</div>
      <div style={{ fontFamily:MONO, fontSize:13, color:c.text, marginTop:3, whiteSpace:'nowrap' }}>{value}</div>
      {state && state!=='on approval'
        ? <div style={{ fontSize:9.5, color:sc, marginTop:1 }}>{state}</div>
        : (state ? <div style={{ fontSize:9.5, color:c.faint, marginTop:1 }}>{state}</div> : null)}
    </div>
  );
}
function Toggle({ c, on }) {
  return <div style={{ width:40, height:24, borderRadius:99, background:on?c.a:c.rule, position:'relative', flexShrink:0 }}>
    <div style={{ position:'absolute', top:3, left:on?19:3, width:18, height:18, borderRadius:'50%', background:'#fff', boxShadow:'0 1px 2px rgba(0,0,0,0.25)' }} /></div>;
}
const solid = (c) => ({ background:c.a, color:c.aOn, border:'none', borderRadius:10, fontFamily:c.f.body, fontSize:14, fontWeight:600, cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center', gap:7 });
const ghost = (c) => ({ background:c.surface, color:c.text, border:`1px solid ${c.faint}`, borderRadius:10, fontFamily:c.f.body, fontSize:14, fontWeight:500, cursor:'pointer' });

/* ---------- photo viewer ---------- */
function PhotoViewer({ c, update, onClose }) {
  const [i, setI] = useState(0);
  return (
    <BottomSheet c={c} onClose={onClose}>
      <div style={{ display:'flex', alignItems:'baseline', justifyContent:'space-between', marginBottom:12 }}>
        <span style={{ fontWeight:600, fontSize:16, color:c.text }}>{update.stage} <span style={{ fontFamily:MONO, fontSize:10.5, color:c.faint, fontWeight:400, marginLeft:6 }}>{update.date}</span></span>
        <span onClick={onClose} style={{ cursor:'pointer', color:c.faint, fontSize:20 }}>×</span>
      </div>
      <Painting {...paintProps(update.photos[i])} radius={12} style={{ width:'100%', aspectRatio: String((update.photos[i] && update.photos[i].ratio) || 0.78) }} />
      {update.photos.length > 1 && (
        <div style={{ display:'flex', gap:8, marginTop:12 }}>
          {update.photos.map((p,idx) => (
            <div key={idx} onClick={() => setI(idx)} style={{ cursor:'pointer', borderRadius:8, padding:2, border:`2px solid ${idx===i?c.a:'transparent'}` }}>
              <Painting {...paintProps(p)} radius={6} style={{ width:52, height:52 }} />
            </div>
          ))}
          <div style={{ marginLeft:'auto', alignSelf:'center', fontFamily:MONO, fontSize:10.5, color:c.faint }}>{i+1} of {update.photos.length}</div>
        </div>
      )}
    </BottomSheet>
  );
}

/* ---------- note sheet (per-stage feedback, posts in-thread) ---------- */
function NoteSheet({ c, update, onClose, onSend }) {
  const [text, setText] = useState('');
  const isFinal = update.id === 'final';
  const post = () => onSend(text.trim() || (isFinal ? 'Could the eyes be a touch warmer?' : 'Could her head tilt up just a little?'));
  return (
    <BottomSheet c={c} onClose={onClose}>
      <div style={{ fontFamily:MONO, fontSize:10, letterSpacing:'0.14em', textTransform:'uppercase', color:c.a }}>On the {update.stage.toLowerCase()}</div>
      <h3 style={{ fontWeight:600, fontSize:20, color:c.text, margin:'7px 0 0' }}>{isFinal ? 'What would you like adjusted?' : 'Suggest a change'}</h3>
      <p style={{ fontSize:13, color:c.muted, lineHeight:1.5, margin:'7px 0 14px' }}>Your note posts right here on the commission, and Iris replies in this thread. Nothing to dig out of your email.</p>
      <Field c={c} label="Your note">
        <textarea rows={4} value={text} onChange={e=>setText(e.target.value)} placeholder={isFinal ? "Could the eyes be a touch warmer?" : "Could her head tilt up just a little?"} style={ta(c)} />
      </Field>
      <button onClick={post} style={{ ...solid(c), width:'100%', padding:'15px' }}>Post note <span style={{ fontSize:16 }}>→</span></button>
    </BottomSheet>
  );
}

/* ---------- approve sheet (the big moment) ---------- */
function ApproveSheet({ c, onClose, onPayNow, onPayLater }) {
  const [done, setDone] = useState(false);
  if (done) {
    return (
      <BottomSheet c={c} onClose={onPayLater}>
        <div style={{ textAlign:'center', padding:'8px 6px 8px', position:'relative', overflow:'hidden' }}>
          <div aria-hidden style={{ position:'absolute', inset:0, pointerEvents:'none' }}>
            {[...Array(14)].map((_,i) => (
              <span key={i} style={{ position:'absolute', top:'-10px', left:`${(i*7+5)%100}%`,
                width:7, height:7, borderRadius:2, background:[c.a,'#C0613C','#B5863A','#5C6536'][i%4],
                animation:`mq-confetti ${1.1+(i%5)*0.18}s ease-in ${(i%6)*0.07}s forwards`, opacity:0.9 }} />
            ))}
          </div>
          <div style={{ width:60, height:60, borderRadius:'50%', background:c.a, color:c.aOn, display:'flex', alignItems:'center', justifyContent:'center', fontSize:30, margin:'10px auto 16px', position:'relative' }}>✓</div>
          <h3 style={{ fontFamily:c.f.head, fontWeight:c.f.hw, fontSize:28, letterSpacing:c.f.kern, color:c.text, margin:0 }}>Maple is yours.</h3>
          <p style={{ fontSize:14, color:c.muted, lineHeight:1.55, margin:'10px auto 0', maxWidth:'32ch' }}>
            You approved the final. One last step, your balance of ${MONEY.balance}, and Iris gets her ready to ship to your door.
          </p>
          <button onClick={onPayNow} style={{ ...solid(c), padding:'15px 30px', margin:'20px auto 0', fontSize:15.5 }}>Pay your ${MONEY.balance} balance</button>
          <div onClick={onPayLater} style={{ fontFamily:MONO, fontSize:10.5, color:c.faint, marginTop:14, cursor:'pointer', textDecoration:'underline' }}>I'll pay in a moment</div>
        </div>
      </BottomSheet>
    );
  }
  return (
    <BottomSheet c={c} onClose={onClose}>
      <div style={{ textAlign:'center' }}>
        <div style={{ fontFamily:MONO, fontSize:10, letterSpacing:'0.16em', textTransform:'uppercase', color:c.a }}>One last look</div>
        <h3 style={{ fontFamily:c.f.body, fontWeight:600, fontSize:26, letterSpacing:'-0.015em', color:c.text, margin:'8px 0 0' }}>Approve your final portrait?</h3>
      </div>
      <div style={{ margin:'16px 0' }}>
        <Painting {...paintProps(STG.final)} radius={12} style={{ width:'100%', maxWidth: Math.round(260 * ((STG.final && STG.final.ratio) || 0.78)), aspectRatio: String((STG.final && STG.final.ratio) || 0.78), margin:'0 auto' }} />
      </div>
      <p style={{ fontSize:13.5, color:c.soft, lineHeight:1.55, margin:'0 0 4px', textAlign:'center' }}>
        Once you approve, Iris considers Maple finished and prepares her to ship. This is the moment to be sure.
      </p>
      <div style={{ background:c.sunk, border:`1px solid ${c.rule}`, borderRadius:11, padding:'12px 14px', margin:'14px 0' }}>
        {[['Approving means', "you're happy with the painting"], ['Your balance', `of $${MONEY.balance}, due on approval`], ['Small tweaks', 'are on me, just send a note instead']].map((r,i) => (
          <div key={i} style={{ display:'flex', justifyContent:'space-between', gap:12, fontSize:12.5, padding:'4px 0', color:c.muted }}>
            <span>{r[0]}</span><span style={{ color:c.text, textAlign:'right' }}>{r[1]}</span>
          </div>
        ))}
      </div>
      <button onClick={() => setDone(true)} style={{ ...solid(c), width:'100%', padding:'16px', fontSize:15.5 }}>Yes, approve the final</button>
      <button onClick={onClose} style={{ ...ghost(c), width:'100%', padding:'13px', marginTop:9 }}>Not yet, let me look again</button>
    </BottomSheet>
  );
}

/* ---------- pay sheet ---------- */
function PaySheet({ c, onClose, onPaid }) {
  const [done, setDone] = useState(false);
  return (
    <BottomSheet c={c} onClose={onClose}>
      {done ? (
        <div style={{ textAlign:'center', padding:'14px 6px 8px' }}>
          <div style={{ width:50, height:50, borderRadius:'50%', background:c.aSoft, color:c.a, display:'flex', alignItems:'center', justifyContent:'center', fontSize:24, margin:'0 auto 14px' }}>✓</div>
          <h3 style={{ fontWeight:600, fontSize:20, color:c.text, margin:0 }}>Paid in full.</h3>
          <p style={{ fontSize:13.5, color:c.muted, lineHeight:1.5, margin:'8px auto 0', maxWidth:'32ch' }}>Iris will get Maple ready and email you tracking the moment she's on her way. Thank you!</p>
          <button onClick={onPaid} style={{ ...solid(c), padding:'12px 24px', margin:'18px auto 0' }}>Done</button>
        </div>
      ) : (
        <React.Fragment>
          <div style={{ fontFamily:MONO, fontSize:10, letterSpacing:'0.14em', textTransform:'uppercase', color:c.a }}>Balance due</div>
          <h3 style={{ fontWeight:600, fontSize:21, color:c.text, margin:'7px 0 13px' }}>Pay your balance</h3>
          <div style={{ background:c.sunk, border:`1px solid ${c.rule}`, borderRadius:12, padding:'12px 14px', marginBottom:14 }}>
            {[['Portrait total', '$'+MONEY.total], ['Deposit paid', '−$'+MONEY.deposit]].map((r,i) => (
              <div key={i} style={{ display:'flex', justifyContent:'space-between', fontSize:13, color:c.muted, padding:'3px 0' }}><span>{r[0]}</span><span style={{ fontFamily:MONO }}>{r[1]}</span></div>
            ))}
            <div style={{ display:'flex', justifyContent:'space-between', fontSize:14.5, color:c.text, padding:'7px 0 0', marginTop:5, borderTop:`1px solid ${c.rule}`, fontWeight:600 }}><span>Balance</span><span style={{ fontFamily:MONO }}>${MONEY.balance}</span></div>
          </div>
          {/* === STRIPE INTEGRATION POINT: balance checkout (button-only defer) ===
              No card data lives in-app. onClick should create a Stripe Checkout Session
              for MONEY.balance on the artist's connected account and redirect; the
              webhook flips the dashboard card to 'done' and frees the public-queue slot.
              The mock setDone(true) stands in for that redirect + success. */}
          <button data-stripe-action="pay-balance" onClick={() => setDone(true)} style={{ ...solid(c), width:'100%', padding:'15px' }}>Pay ${MONEY.balance}</button>
          <div style={{ display:'flex', alignItems:'center', justifyContent:'center', gap:7, marginTop:12, fontFamily:MONO, fontSize:10.5, color:c.faint }}>
            <span style={{ fontSize:11 }}>🔒</span>
            <span>You'll finish on Stripe's secure checkout. Iris is paid directly.</span>
          </div>
        </React.Fragment>
      )}
    </BottomSheet>
  );
}

/* ============================================================ app + tweaks */
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "teal",
  "tone": "warm",
  "type": "editorial",
  "stage": "review"
}/*EDITMODE-END*/;

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  return (
    <React.Fragment>
      <PhoneShell t={t} url="mella.studio/c/xK9mP2" screenId="mq-screen"><ClientPortal t={t} /></PhoneShell>
      <TweaksPanel title="Tweaks">
        <TweakSection label="The artist's brand" />
        <TweakColor label="Accent" value={ACCENTS[t.accent]}
          options={Object.values(ACCENTS)}
          onChange={(v) => setTweak('accent', Object.keys(ACCENTS).find(k => ACCENTS[k] === v) || 'teal')} />
        <TweakRadio label="Background" value={t.tone} options={['warm','paper','dusk']}
          onChange={(v) => setTweak('tone', v)} />
        <TweakSelect label="Type" value={t.type}
          options={[['editorial','Editorial serif'],['classic','Classic serif'],['clean','Clean sans']]}
          onChange={(v) => setTweak('type', v)} />
        <TweakSection label="Commission stage" />
        <TweakSelect label="Stage" value={t.stage}
          options={[['deposit','Deposit checkout'],['confirmed','Just confirmed'],['progress','Mid-progress (no final yet)'],['review','Final posted, awaiting approval'],['balance','Balance due'],['delivered','Paid + shipped']]}
          onChange={(v) => setTweak('stage', v)} />
      </TweaksPanel>
    </React.Fragment>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
