/* global React, WillowMark, Icon */
/* Willow Initiative — Chapter Officer Portal
   Sign up (chapter-code gated) · Sign in · Dashboard.
   Uses window.__sb (Supabase) wired in index.html.
   Light / warm-paper theme to match the rest of the site. */

const SB_URL = 'https://dapjtmywlqzaopxjvveb.supabase.co';
const SB_ANON = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImRhcGp0bXl3bHF6YW9weGp2dmViIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Nzk2NTk3MDUsImV4cCI6MjA5NTIzNTcwNX0.giZgLopuAh4YndIz99owkQzEU_Gg0PMlfO9B_gOOwJU';

/* ---------- small shared bits ---------- */

function PMono({ children, style }) {
  return (
    <span style={{ fontFamily: 'var(--f-mono)', fontSize: 11, letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--ink-mute)', ...style }}>
      {children}
    </span>
  );
}

function PField({ label, value, onChange, onEnter, type = 'text', placeholder, autoFocus, right }) {
  const ref = React.useRef(null);
  React.useEffect(() => { if (autoFocus && ref.current) ref.current.focus(); }, [autoFocus]);
  return (
    <label style={{ display: 'block' }}>
      <div style={{ marginBottom: 9 }}><PMono>{label}</PMono></div>
      <div style={{ position: 'relative' }}>
        <input
          ref={ref}
          type={type}
          value={value}
          placeholder={placeholder}
          onChange={(e) => onChange(e.target.value)}
          onKeyDown={(e) => { if (e.key === 'Enter' && onEnter) { e.preventDefault(); onEnter(); } }}
          className="portal-input"
          style={{
            width: '100%', boxSizing: 'border-box',
            padding: '15px 16px', paddingRight: right ? 92 : 16,
            background: 'var(--surface)',
            border: '1px solid var(--line-strong)',
            borderRadius: 'var(--r-input)',
            color: 'var(--ink)', fontSize: 16, fontFamily: 'var(--f-sans)',
            outline: 'none', transition: 'border-color 160ms, box-shadow 160ms',
          }}
          onFocus={(e) => { e.target.style.borderColor = 'var(--sage)'; e.target.style.boxShadow = '0 0 0 3px rgba(92,124,95,0.12)'; }}
          onBlur={(e) => { e.target.style.borderColor = 'var(--line-strong)'; e.target.style.boxShadow = 'none'; }}
        />
        {right && <div style={{ position: 'absolute', right: 12, top: '50%', transform: 'translateY(-50%)' }}>{right}</div>}
      </div>
    </label>
  );
}

function PButton({ children, onClick, busy, disabled, variant = 'primary', type = 'button' }) {
  const primary = variant === 'primary';
  return (
    <button
      type={type}
      onClick={onClick}
      disabled={disabled || busy}
      style={{
        width: '100%', padding: '15px 20px', borderRadius: 'var(--r-pill)',
        border: primary ? 'none' : '1px solid var(--line-strong)',
        background: primary ? 'var(--sage)' : 'transparent',
        color: primary ? '#fff' : 'var(--ink)', fontSize: 15, fontWeight: 600, fontFamily: 'var(--f-sans)',
        cursor: disabled || busy ? 'not-allowed' : 'pointer',
        opacity: disabled || busy ? 0.6 : 1,
        boxShadow: primary ? 'var(--shadow-1)' : 'none',
        transition: 'opacity 160ms, transform 160ms, filter 160ms',
        display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
      }}
      onMouseDown={(e) => { if (!disabled && !busy) e.currentTarget.style.transform = 'scale(0.985)'; }}
      onMouseUp={(e) => { e.currentTarget.style.transform = 'scale(1)'; }}
      onMouseLeave={(e) => { e.currentTarget.style.transform = 'scale(1)'; }}
    >
      {busy && <span className="portal-spin" style={{ width: 15, height: 15, border: '2px solid rgba(255,255,255,0.45)', borderTopColor: '#fff', borderRadius: '50%', display: 'inline-block' }} />}
      {busy ? 'One moment…' : children}
    </button>
  );
}

function ShowToggle({ on, onToggle }) {
  return (
    <button onClick={onToggle} style={{ background: 'none', border: 'none', color: 'var(--ink-mute)', fontSize: 12, fontFamily: 'var(--f-mono)', letterSpacing: '0.08em', cursor: 'pointer', textTransform: 'uppercase' }}>
      {on ? 'Hide' : 'Show'}
    </button>
  );
}

/* ---------- signup steps ---------- */

const SIGNUP_STEPS = [
  { key: 'code',     label: 'Chapter code', q: 'Enter your chapter code', sub: "The code your chapter lead gave you. It links you to your chapter and city." },
  { key: 'name',     label: 'Full name',    q: "What's your name?",       sub: 'This is how you’ll appear in the portal.' },
  { key: 'email',    label: 'Email',        q: "What's your email?",      sub: "You’ll use this to sign in." },
  { key: 'password', label: 'Password',     q: 'Create a password',       sub: 'At least 8 characters.' },
];

function Signup({ sb, switchToSignin }) {
  const [step, setStep]   = React.useState(0);
  const [form, setForm]   = React.useState({ code: '', city: '', name: '', email: '', password: '' });
  const [chapter, setChapter] = React.useState(null);
  const [error, setError] = React.useState('');
  const [busy, setBusy]   = React.useState(false);
  const [showPw, setShowPw] = React.useState(false);
  const [done, setDone]   = React.useState(null);

  const set = (k, v) => setForm((f) => ({ ...f, [k]: v }));
  const cur = SIGNUP_STEPS[step];

  async function validateCode() {
    setError('');
    const code = form.code.trim();
    if (!code) { setError('Please enter your chapter code.'); return; }
    setBusy(true);
    const { data, error: e } = await sb.rpc('lookup_chapter', { p_code: code });
    setBusy(false);
    if (e) { setError('Could not check that code. Please try again.'); return; }
    const row = Array.isArray(data) ? data[0] : data;
    if (!row) { setError('That code didn’t match any chapter. Double-check with your chapter lead.'); return; }
    setChapter(row);
    if (row.city && !form.city) set('city', row.city);
    setStep(1);
  }

  function next() {
    setError('');
    if (cur.key === 'code')  return validateCode();
    if (cur.key === 'name'  && !form.name.trim())  return setError('Please enter your name.');
    if (cur.key === 'email' && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email.trim())) return setError('Please enter a valid email.');
    if (cur.key === 'password') return submit();
    setStep((s) => Math.min(SIGNUP_STEPS.length - 1, s + 1));
  }

  function back() { setError(''); setStep((s) => Math.max(0, s - 1)); }

  async function submit() {
    if (form.password.length < 8) { setError('Password must be at least 8 characters.'); return; }
    setBusy(true); setError('');
    try {
      const res = await fetch(`${SB_URL}/functions/v1/register-officer`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${SB_ANON}`, 'apikey': SB_ANON },
        body: JSON.stringify({ code: form.code.trim(), name: form.name.trim(), email: form.email.trim(), password: form.password }),
      });
      const out = await res.json().catch(() => ({}));
      if (!res.ok) { setBusy(false); setError(out.error || 'Could not create your account. Please try again.'); return; }
      // Account is created and confirmed — sign straight in to the dashboard.
      const { error: signInErr } = await sb.auth.signInWithPassword({ email: form.email.trim(), password: form.password });
      setBusy(false);
      if (signInErr) { setDone('created'); return; } // fallback: prompt manual sign in
      // success: onAuthStateChange swaps this view for the dashboard.
    } catch (_e) {
      setBusy(false);
      setError('Network error. Please try again.');
    }
  }

  if (done === 'created') {
    return (
      <div style={{ textAlign: 'center' }}>
        <div style={{ width: 56, height: 56, margin: '0 auto 22px', borderRadius: 18, background: 'var(--sage-tint)', display: 'grid', placeItems: 'center' }}>
          <Icon name="check" size={26} stroke={2} style={{ color: 'var(--sage)' }} />
        </div>
        <h2 style={{ color: 'var(--ink)', fontSize: 26, fontWeight: 500, letterSpacing: '-0.02em', margin: '0 0 12px' }}>Account created</h2>
        <p style={{ color: 'var(--ink-soft)', fontSize: 15, lineHeight: 1.6, margin: '0 0 26px' }}>
          Your officer account for <span style={{ color: 'var(--ink)', fontWeight: 500 }}>{form.email}</span> is ready. Sign in to enter your dashboard.
        </p>
        <PButton onClick={switchToSignin}>Go to sign in</PButton>
      </div>
    );
  }

  const value = form[cur.key];
  const progress = ((step + 1) / SIGNUP_STEPS.length) * 100;

  return (
    <div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 26 }}>
        <div style={{ flex: 1, height: 3, background: 'var(--line-strong)', borderRadius: 99, overflow: 'hidden' }}>
          <div style={{ width: `${progress}%`, height: '100%', background: 'var(--sage)', borderRadius: 99, transition: 'width 360ms var(--ease-out)' }} />
        </div>
        <PMono style={{ flexShrink: 0 }}>{step + 1}/{SIGNUP_STEPS.length}</PMono>
      </div>

      {chapter && step > 0 && (
        <div style={{ display: 'inline-flex', alignItems: 'center', gap: 8, padding: '7px 13px', borderRadius: 99, background: 'var(--sage-tint)', border: '1px solid rgba(92,124,95,0.28)', marginBottom: 20 }}>
          <span style={{ width: 7, height: 7, borderRadius: '50%', background: 'var(--sage)' }} />
          <span style={{ color: 'var(--ink)', fontSize: 13, fontWeight: 500 }}>{chapter.name}</span>
          <span style={{ color: 'var(--ink-mute)', fontSize: 12 }}>· {chapter.city}</span>
        </div>
      )}

      <div key={step} className="portal-step">
        <h2 style={{ color: 'var(--ink)', fontSize: 'clamp(24px,4vw,30px)', fontWeight: 500, letterSpacing: '-0.025em', lineHeight: 1.1, margin: '0 0 8px' }}>{cur.q}</h2>
        <p style={{ color: 'var(--ink-soft)', fontSize: 14.5, lineHeight: 1.55, margin: '0 0 24px' }}>{cur.sub}</p>

        <PField
          label={cur.label}
          value={value}
          onChange={(v) => set(cur.key, v)}
          onEnter={next}
          autoFocus
          type={cur.key === 'password' && !showPw ? 'password' : cur.key === 'email' ? 'email' : 'text'}
          placeholder={
            cur.key === 'code' ? 'e.g. FRISCO-K7Q2' :
            cur.key === 'city' ? 'e.g. Frisco, Texas' :
            cur.key === 'name' ? 'First and last name' :
            cur.key === 'email' ? 'you@email.com' : '••••••••'
          }
          right={cur.key === 'password' ? <ShowToggle on={showPw} onToggle={() => setShowPw((s) => !s)} /> : null}
        />

        {error && <p style={{ color: 'var(--danger)', fontSize: 13.5, margin: '14px 2px 0' }}>{error}</p>}

        <div style={{ display: 'flex', gap: 10, marginTop: 24 }}>
          {step > 0 && (
            <button onClick={back} style={{ flexShrink: 0, padding: '15px 22px', borderRadius: 'var(--r-pill)', border: '1px solid var(--line-strong)', background: 'transparent', color: 'var(--ink-soft)', fontSize: 15, fontWeight: 500, cursor: 'pointer' }}>Back</button>
          )}
          <div style={{ flex: 1 }}>
            <PButton onClick={next} busy={busy}>{cur.key === 'password' ? 'Create account' : 'Continue'}</PButton>
          </div>
        </div>
      </div>

      <div style={{ marginTop: 26, textAlign: 'center' }}>
        <span style={{ color: 'var(--ink-mute)', fontSize: 14 }}>Already have an account? </span>
        <button onClick={switchToSignin} style={{ background: 'none', border: 'none', color: 'var(--sage)', fontSize: 14, fontWeight: 600, cursor: 'pointer', padding: 0 }}>Sign in</button>
      </div>
    </div>
  );
}

/* ---------- sign in ---------- */

function Signin({ sb, switchToSignup }) {
  const [email, setEmail] = React.useState('');
  const [password, setPassword] = React.useState('');
  const [showPw, setShowPw] = React.useState(false);
  const [error, setError] = React.useState('');
  const [busy, setBusy] = React.useState(false);

  async function submit() {
    setError('');
    if (!email.trim() || !password) { setError('Enter your email and password.'); return; }
    setBusy(true);
    const { error: e } = await sb.auth.signInWithPassword({ email: email.trim(), password });
    setBusy(false);
    if (e) {
      const m = (e.message || '').toLowerCase();
      if (m.includes('confirm')) setError('Please verify your email first — check your inbox for the link.');
      else setError('Incorrect email or password.');
      return;
    }
  }

  return (
    <div>
      <PMono style={{ display: 'block', marginBottom: 14 }}>Officer sign in</PMono>
      <h2 style={{ color: 'var(--ink)', fontSize: 'clamp(26px,4vw,34px)', fontWeight: 500, letterSpacing: '-0.03em', lineHeight: 1.05, margin: '0 0 8px' }}>
        Welcome back to your<br /><span style={{ fontFamily: 'var(--f-serif)', fontStyle: 'italic', color: 'var(--sage)' }}>chapter portal.</span>
      </h2>
      <p style={{ color: 'var(--ink-soft)', fontSize: 14.5, margin: '0 0 28px' }}>Sign in to manage your Willow chapter.</p>

      <div style={{ display: 'grid', gap: 16 }}>
        <PField label="Email" value={email} onChange={setEmail} onEnter={submit} type="email" placeholder="you@email.com" autoFocus />
        <PField
          label="Password" value={password} onChange={setPassword} onEnter={submit}
          type={showPw ? 'text' : 'password'} placeholder="••••••••"
          right={<ShowToggle on={showPw} onToggle={() => setShowPw((s) => !s)} />}
        />
      </div>

      {error && <p style={{ color: 'var(--danger)', fontSize: 13.5, margin: '16px 2px 0' }}>{error}</p>}

      <div style={{ marginTop: 24 }}><PButton onClick={submit} busy={busy}>Sign in</PButton></div>

      <div style={{ marginTop: 26, textAlign: 'center' }}>
        <span style={{ color: 'var(--ink-mute)', fontSize: 14 }}>First time here? </span>
        <button onClick={switchToSignup} style={{ background: 'none', border: 'none', color: 'var(--sage)', fontSize: 14, fontWeight: 600, cursor: 'pointer', padding: 0 }}>Create an account</button>
      </div>
    </div>
  );
}

/* ---------- dashboard: shared chapter setup ---------- */

const CORE_TASKS = [
  { key: 'officers', icon: 'users', title: 'Lock in your officer team',
    desc: 'Confirm who your chapter officers are — president, VP, and any leads. Once your team is set, mark this done.' },
  { key: 'members_gc', icon: 'chat', title: 'Recruit 10 members & start a WhatsApp group',
    desc: 'Find at least 10 members and spin up a WhatsApp group chat to coordinate. Drop the invite link, then confirm.',
    input: { placeholder: 'https://chat.whatsapp.com/…' } },
  { key: 'first_meeting', icon: 'sparkle', title: 'Host your first meeting',
    desc: 'Hold your first chapter meeting, then upload a few photos from it.', upload: true },
];

const EXTRA_TASKS = [
  { key: 'instagram', icon: 'heart', title: 'Create your chapter Instagram',
    desc: 'Make an Instagram account for your chapter so people can follow along.',
    input: { placeholder: '@yourchapter' } },
  { key: 'meet_officers', icon: 'book', title: 'Post “Meet the Officers”',
    desc: 'Share your first Instagram post introducing your officer team.',
    input: { placeholder: 'https://instagram.com/p/…' } },
  { key: 'outreach_event', icon: 'pulse', title: 'Plan an outreach event',
    desc: 'Organize your chapter’s first community awareness or outreach event.' },
];

function TaskCard({ t, state, locked, n, onSave, onUpload, officerName }) {
  const done = !!(state && state.done);
  const images = (state && state.images) || [];
  const [note, setNote] = React.useState((state && state.note) || '');
  const [busy, setBusy] = React.useState(false);
  React.useEffect(() => { setNote((state && state.note) || ''); }, [state && state.note]);

  async function run(fn) { setBusy(true); try { await fn(); } finally { setBusy(false); } }
  const confirm  = () => run(() => onSave(t.key, { done: true, note: note.trim() || undefined, by: officerName, at: new Date().toISOString() }));
  const undo     = () => run(() => onSave(t.key, { ...(state || {}), done: false }));
  const saveNote = () => run(() => onSave(t.key, { ...(state || {}), done, note: note.trim() || undefined }));
  const onFiles  = (e) => { const f = e.target.files; if (f && f.length) run(async () => { await onUpload(t.key, f); e.target.value = ''; }); };

  return (
    <div style={{
      position: 'relative', padding: 'clamp(18px,3vw,24px)', borderRadius: 20,
      background: done ? 'var(--sage-tint)' : 'var(--surface)',
      border: `1px solid ${done ? 'rgba(92,124,95,0.35)' : 'var(--line)'}`,
      opacity: locked ? 0.5 : 1, transition: 'opacity 200ms, background 200ms, border-color 200ms',
    }}>
      <div style={{ display: 'flex', gap: 14, alignItems: 'flex-start' }}>
        <span style={{ flexShrink: 0, width: 38, height: 38, borderRadius: 11, background: done ? 'var(--sage)' : 'var(--bg-2)', color: done ? '#fff' : 'var(--ink-soft)', display: 'grid', placeItems: 'center' }}>
          {done ? <Icon name="check" size={20} stroke={2.4} /> : (n ? <span style={{ fontWeight: 600, fontSize: 15 }}>{n}</span> : <Icon name={t.icon} size={19} />)}
        </span>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 9, flexWrap: 'wrap' }}>
            <h3 style={{ margin: 0, color: 'var(--ink)', fontSize: 16.5, fontWeight: 600, letterSpacing: '-0.01em' }}>{t.title}</h3>
            {done && <PMono style={{ color: 'var(--sage)' }}>Done</PMono>}
            {locked && <PMono>Locked</PMono>}
          </div>
          <p style={{ margin: '6px 0 0', color: 'var(--ink-soft)', fontSize: 14, lineHeight: 1.55 }}>{t.desc}</p>

          {!locked && (
            <div style={{ marginTop: 14 }}>
              {t.input && (
                <input value={note} onChange={(e) => setNote(e.target.value)} onBlur={saveNote} placeholder={t.input.placeholder}
                  className="portal-input"
                  style={{ width: '100%', boxSizing: 'border-box', marginBottom: 12, padding: '11px 14px', background: 'var(--surface)', border: '1px solid var(--line-strong)', borderRadius: 12, color: 'var(--ink)', fontSize: 14, outline: 'none' }} />
              )}

              {t.upload && (
                <div style={{ marginBottom: 12 }}>
                  {images.length > 0 && (
                    <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 10 }}>
                      {images.map((u, i) => (
                        <a key={i} href={u} target="_blank" rel="noopener noreferrer">
                          <img src={u} alt="meeting" style={{ width: 64, height: 64, objectFit: 'cover', borderRadius: 10, border: '1px solid var(--line)', display: 'block' }} />
                        </a>
                      ))}
                    </div>
                  )}
                  <label style={{ display: 'inline-flex', alignItems: 'center', gap: 8, padding: '9px 16px', borderRadius: 999, border: '1px dashed var(--line-strong)', color: 'var(--ink-soft)', fontSize: 13.5, fontWeight: 500, cursor: 'pointer' }}>
                    <Icon name="plus" size={15} /> {images.length ? 'Add more photos' : 'Upload photos'}
                    <input type="file" accept="image/*" multiple onChange={onFiles} style={{ display: 'none' }} />
                  </label>
                </div>
              )}

              <div style={{ display: 'flex', alignItems: 'center', gap: 12, minHeight: 22 }}>
                {!t.upload && !done && (
                  <button onClick={confirm} disabled={busy} style={{ padding: '10px 20px', borderRadius: 999, border: 'none', background: 'var(--sage)', color: '#fff', fontSize: 14, fontWeight: 600, cursor: busy ? 'wait' : 'pointer', opacity: busy ? 0.6 : 1 }}>Mark complete</button>
                )}
                {done && (
                  <button onClick={undo} disabled={busy} style={{ padding: '8px 16px', borderRadius: 999, border: '1px solid var(--line-strong)', background: 'transparent', color: 'var(--ink-soft)', fontSize: 13, fontWeight: 500, cursor: 'pointer' }}>Undo</button>
                )}
                {busy && <span className="portal-spin" style={{ width: 15, height: 15, border: '2px solid var(--line-strong)', borderTopColor: 'var(--sage)', borderRadius: '50%', display: 'inline-block' }} />}
                {done && state && state.by && <span style={{ color: 'var(--ink-mute)', fontSize: 12 }}>by {state.by}</span>}
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

function Dashboard({ sb, session, go }) {
  const [profile, setProfile] = React.useState(null);
  const [tasks, setTasks] = React.useState({});
  const [chapterId, setChapterId] = React.useState(null);

  React.useEffect(() => {
    let alive = true;
    (async () => {
      const { data } = await sb.from('officers')
        .select('full_name, email, role, chapter:chapters(id, name, city)')
        .eq('id', session.user.id).maybeSingle();
      if (!alive) return;
      setProfile(data);
      const cid = (data && data.chapter && data.chapter.id) || null;
      setChapterId(cid);
      if (cid) {
        const { data: prog } = await sb.from('chapter_progress').select('tasks').eq('chapter_id', cid).maybeSingle();
        if (alive) setTasks((prog && prog.tasks) || {});
      }
    })();
    return () => { alive = false; };
  }, [session]);

  React.useEffect(() => {
    if (!chapterId) return;
    const ch = sb.channel('cp-' + chapterId)
      .on('postgres_changes', { event: '*', schema: 'public', table: 'chapter_progress', filter: `chapter_id=eq.${chapterId}` },
        (payload) => { if (payload.new && payload.new.tasks) setTasks(payload.new.tasks); })
      .subscribe();
    return () => { sb.removeChannel(ch); };
  }, [chapterId]);

  const chapter = profile && profile.chapter;
  const officerName = (profile && profile.full_name) || (session.user.email || '').split('@')[0];
  const firstName = officerName.split(' ')[0];

  const saveTask = async (key, value) => {
    setTasks((t) => ({ ...t, [key]: value }));
    const { data } = await sb.rpc('set_chapter_task', { p_key: key, p_value: value });
    if (data) setTasks(data);
  };

  const uploadFiles = async (key, fileList) => {
    if (!chapterId) return;
    const urls = [ ...((tasks[key] && tasks[key].images) || []) ];
    for (const f of Array.from(fileList)) {
      const safe = f.name.replace(/[^a-zA-Z0-9.\-_]/g, '_');
      const path = `${chapterId}/${key}/${Date.now()}-${safe}`;
      const { error } = await sb.storage.from('chapter-uploads').upload(path, f);
      if (!error) {
        const pub = sb.storage.from('chapter-uploads').getPublicUrl(path);
        if (pub && pub.data && pub.data.publicUrl) urls.push(pub.data.publicUrl);
      }
    }
    await saveTask(key, { done: urls.length > 0, images: urls, by: officerName, at: new Date().toISOString() });
  };

  const all = [...CORE_TASKS, ...EXTRA_TASKS];
  const doneCount = all.filter((t) => tasks[t.key] && tasks[t.key].done).length;
  const pct = Math.round((doneCount / all.length) * 100);
  const coreUnlocked = (i) => i === 0 || (tasks[CORE_TASKS[i - 1].key] && tasks[CORE_TASKS[i - 1].key].done);

  return (
    <div style={{ width: '100%', maxWidth: 760, margin: '0 auto' }}>
      {/* top bar */}
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 14, marginBottom: 'clamp(32px,5vw,48px)' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 11 }}>
          <WillowMark size={32} />
          <div>
            <div style={{ color: 'var(--ink)', fontWeight: 600, fontSize: 15, letterSpacing: '-0.01em' }}>Chapter Portal</div>
            <PMono style={{ fontSize: 10 }}>Willow Initiative</PMono>
          </div>
        </div>
        <button onClick={() => sb.auth.signOut()} style={{ padding: '10px 18px', borderRadius: 'var(--r-pill)', border: '1px solid var(--line-strong)', background: 'transparent', color: 'var(--ink-soft)', fontSize: 13.5, fontWeight: 500, cursor: 'pointer' }}>Sign out</button>
      </div>

      {/* welcome */}
      <div style={{ marginBottom: 28 }}>
        <PMono style={{ display: 'block', marginBottom: 12 }}>{chapter ? `${chapter.name} chapter` : 'Your chapter'}</PMono>
        <h1 style={{ color: 'var(--ink)', fontSize: 'clamp(30px,5vw,48px)', fontWeight: 500, letterSpacing: '-0.035em', lineHeight: 1, margin: '0 0 16px' }}>
          Welcome back,<br /><span style={{ fontFamily: 'var(--f-serif)', fontStyle: 'italic', color: 'var(--sage)' }}>{firstName || 'officer'}.</span>
        </h1>
        <p style={{ color: 'var(--ink-soft)', fontSize: 15.5, lineHeight: 1.6, maxWidth: '54ch', margin: 0 }}>
          This is your chapter’s shared workspace — every officer with your code sees the same checklist, so you can build your chapter together.
        </p>
      </div>

      {/* progress */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 30 }}>
        <div style={{ flex: 1, height: 8, background: 'var(--bg-2)', borderRadius: 99, overflow: 'hidden' }}>
          <div style={{ width: `${pct}%`, height: '100%', background: 'var(--sage)', borderRadius: 99, transition: 'width 400ms var(--ease-out)' }} />
        </div>
        <span style={{ flexShrink: 0, color: 'var(--ink)', fontSize: 14, fontWeight: 600 }}>{doneCount}<span style={{ color: 'var(--ink-mute)', fontWeight: 400 }}>/{all.length} done</span></span>
      </div>

      {/* roadmap */}
      <PMono style={{ display: 'block', marginBottom: 14 }}>Your roadmap — in order</PMono>
      <div style={{ display: 'grid', gap: 12, marginBottom: 34 }}>
        {CORE_TASKS.map((t, i) => (
          <TaskCard key={t.key} t={t} n={i + 1} state={tasks[t.key]} locked={!coreUnlocked(i)}
            onSave={saveTask} onUpload={uploadFiles} officerName={officerName} />
        ))}
      </div>

      {/* extra */}
      <PMono style={{ display: 'block', marginBottom: 14 }}>Keep building — anytime</PMono>
      <div style={{ display: 'grid', gap: 12 }}>
        {EXTRA_TASKS.map((t) => (
          <TaskCard key={t.key} t={t} state={tasks[t.key]} locked={false}
            onSave={saveTask} onUpload={uploadFiles} officerName={officerName} />
        ))}
      </div>

      <div style={{ marginTop: 34, textAlign: 'center' }}>
        <button onClick={() => go('home')} style={{ background: 'none', border: 'none', color: 'var(--ink-mute)', fontSize: 13.5, cursor: 'pointer', padding: 0 }}>← Back to willowinitiative.org</button>
      </div>
    </div>
  );
}

/* ---------- root ---------- */

function Portal({ go }) {
  const sb = window.__sb;
  const [session, setSession] = React.useState(undefined);
  const [view, setView] = React.useState('signin');

  React.useEffect(() => {
    if (!sb) { setSession(null); return; }
    sb.auth.getSession().then(({ data }) => setSession(data.session || null));
    const { data: sub } = sb.auth.onAuthStateChange((_e, s) => setSession(s || null));
    return () => sub.subscription.unsubscribe();
  }, []);

  const shell = (children, narrow = true) => (
    <div style={{ background: 'var(--bg)', minHeight: '100vh', display: 'flex', alignItems: narrow ? 'center' : 'flex-start', justifyContent: 'center', padding: 'clamp(96px,14vh,150px) var(--gutter) clamp(48px,8vh,90px)' }}>
      {narrow
        ? <div style={{ width: '100%', maxWidth: 430, background: 'var(--surface)', border: '1px solid var(--line)', borderRadius: 'var(--r-card)', padding: 'clamp(28px,5vw,44px)', boxShadow: 'var(--shadow-2)' }}>{children}</div>
        : children}
    </div>
  );

  if (!sb) return shell(<p style={{ color: 'var(--ink-soft)', textAlign: 'center' }}>Portal is temporarily unavailable. Please try again shortly.</p>);

  if (session === undefined) {
    return shell(
      <div style={{ display: 'grid', placeItems: 'center', gap: 14 }}>
        <span className="portal-spin" style={{ width: 26, height: 26, border: '2.5px solid var(--line-strong)', borderTopColor: 'var(--sage)', borderRadius: '50%', display: 'inline-block' }} />
        <PMono>Loading portal</PMono>
      </div>
    );
  }

  if (session) return shell(<Dashboard sb={sb} session={session} go={go} />, false);

  return shell(
    view === 'signin'
      ? <Signin sb={sb} switchToSignup={() => setView('signup')} />
      : <Signup sb={sb} switchToSignin={() => setView('signin')} />
  );
}

window.Portal = Portal;
