// DiveDex — Screens A: Welcome, Levels, Dashboard, Log Dive, Sightings, Recap
const { useState, useEffect, useMemo } = React;

// Convert an ISO timestamp to/from the value expected by <input type="datetime-local">.
// datetime-local uses local time "YYYY-MM-DDTHH:MM" with no timezone.
function isoToDatetimeLocal(iso) {
  if (!iso) return '';
  const d = new Date(iso);
  if (isNaN(d.getTime())) return '';
  const pad = (n) => String(n).padStart(2, '0');
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
function datetimeLocalToIso(local) {
  if (!local) return null;
  const d = new Date(local); // browser parses as local time
  if (isNaN(d.getTime())) return null;
  return d.toISOString();
}

// LockScreen — first screen any non-authenticated user sees.
// Always offers a user-pick (Rick / Siet). Shows a password input only if
// the shared password is set. Once unlocked, state.locked → false and
// state.activeUserId → the picked user; persists, so a normal browser
// reload skips this screen until the user explicitly Locks from Settings.
function LockScreen() {
  const { state, actions } = useDiveDex();
  const rick = state.users.find(u => u.id === 'rick');
  const siet = state.users.find(u => u.id === 'siet');
  const [picked, setPicked] = useState(state.activeUserId || null);
  const [password, setPassword] = useState('');
  const [error, setError] = useState(null);
  const needsPassword = !!state.authPassword;

  // First-run affordance: when no password is set, offer to create one
  // inline so users don't have to unlock-then-go-to-Settings.
  const [addPwOpen, setAddPwOpen] = useState(false);
  const [newPw, setNewPw] = useState('');
  const [newPwConfirm, setNewPwConfirm] = useState('');

  const handleUnlock = () => {
    if (!needsPassword && addPwOpen) {
      if (!newPw) {
        setError('Type a password or close the password-setup card.');
        return;
      }
      if (newPw !== newPwConfirm) {
        setError('Passwords don\'t match.');
        return;
      }
      const result = actions.unlockApp(picked, '', { setNewPassword: newPw });
      if (!result.ok) setError(result.reason);
      else setError(null);
      return;
    }
    const result = actions.unlockApp(picked, password);
    if (!result.ok) setError(result.reason);
    else setError(null);
  };
  const onKey = (e) => { if (e.key === 'Enter') handleUnlock(); };
  const userCard = (u) => {
    const isOn = picked === u.id;
    return (
      <button key={u.id} onClick={() => setPicked(u.id)} className="dd-btn" style={{
        flex: 1, padding: '18px 12px', borderRadius: 18,
        background: isOn
          ? `linear-gradient(160deg, ${u.color}22, rgba(11,42,68,0.5))`
          : 'rgba(255,255,255,0.04)',
        border: isOn ? `1.5px solid ${u.color}` : '1px solid rgba(130,236,255,0.14)',
        boxShadow: isOn ? `0 0 22px ${u.color}55, inset 0 0 0 1px ${u.color}33` : 'none',
        color: '#EAF5FF',
        display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 10,
        cursor: 'pointer',
      }}>
        <Avatar name={u.displayName} color={u.color} size={56}/>
        <div style={{ fontSize: 16, fontWeight: 700, fontFamily: 'Space Grotesk' }}>{u.displayName}</div>
        <div style={{ fontSize: 10, color: isOn ? u.color : 'var(--ink-3)', letterSpacing: '0.1em', fontWeight: 700, textTransform: 'uppercase' }}>{u.defaultTitle}</div>
      </button>
    );
  };
  return (
    <div className="dd-screen" style={{ padding: '78px 22px 40px', display: 'flex', flexDirection: 'column', gap: 18 }}>
      <div style={{ display: 'grid', placeItems: 'center', marginBottom: 6 }}>
        <Logo size={36}/>
      </div>
      <div style={{ textAlign: 'center' }}>
        <div className="dd-h-section" style={{ color: '#82ECFF' }}>WHO'S DIVING</div>
        <div className="dd-h1" style={{ fontSize: 22, marginTop: 4 }}>Pick a profile.</div>
        <div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 6, lineHeight: 1.4 }}>
          {needsPassword
            ? 'Choose your profile and enter the shared password to continue.'
            : 'Choose your profile to continue. No password is set yet — anyone with this device can unlock. Add one below if you want to keep things private.'}
        </div>
      </div>
      <div style={{ display: 'flex', gap: 10 }}>
        {rick && userCard(rick)}
        {siet && userCard(siet)}
      </div>
      {needsPassword && (
        <div>
          <div className="dd-h-section" style={{ marginBottom: 6 }}>Password</div>
          <div className="dd-glass" style={{ padding: '10px 12px', borderRadius: 12 }}>
            <input
              type="password"
              value={password}
              onChange={e => setPassword(e.target.value)}
              onKeyDown={onKey}
              autoFocus
              placeholder="Enter shared password"
              style={{
                background: 'transparent', border: 0, outline: 'none', color: '#EAF5FF',
                width: '100%', fontFamily: 'Manrope', fontSize: 15, fontWeight: 600,
              }}
            />
          </div>
        </div>
      )}

      {!needsPassword && (
        <div>
          {!addPwOpen ? (
            <button onClick={() => { setAddPwOpen(true); setError(null); }} className="dd-btn dd-btn-ghost" style={{
              width: '100%', height: 42, borderRadius: 12, fontSize: 12.5, fontWeight: 700,
              borderStyle: 'dashed',
              display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
            }}>
              <span style={{ color: '#82ECFF' }}>＋</span>
              Add a shared password
            </button>
          ) : (
            <div className="dd-glass" style={{ padding: 12, borderRadius: 14, display: 'flex', flexDirection: 'column', gap: 8 }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                <div className="dd-h-section" style={{ margin: 0 }}>New shared password</div>
                <button onClick={() => { setAddPwOpen(false); setNewPw(''); setNewPwConfirm(''); setError(null); }} className="dd-btn" style={{
                  background: 'transparent', color: 'var(--ink-3)', fontSize: 12, fontWeight: 600,
                }}>Cancel</button>
              </div>
              <input
                type="password"
                value={newPw}
                onChange={e => setNewPw(e.target.value)}
                placeholder="Pick a shared password"
                autoFocus
                style={{
                  width: '100%', padding: '8px 10px', borderRadius: 10, fontSize: 13,
                  background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(130,236,255,0.14)',
                  color: '#EAF5FF', outline: 'none',
                }}
              />
              <input
                type="password"
                value={newPwConfirm}
                onChange={e => setNewPwConfirm(e.target.value)}
                onKeyDown={onKey}
                placeholder="Confirm"
                style={{
                  width: '100%', padding: '8px 10px', borderRadius: 10, fontSize: 13,
                  background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(130,236,255,0.14)',
                  color: '#EAF5FF', outline: 'none',
                }}
              />
              <div style={{ fontSize: 10.5, color: 'var(--ink-3)', lineHeight: 1.4 }}>
                Both you and your buddy will use this to unlock. You can change or clear it later in Settings.
              </div>
            </div>
          )}
        </div>
      )}
      {error && (
        <div style={{
          padding: '10px 12px', borderRadius: 10, fontSize: 12, fontWeight: 600,
          background: 'rgba(255,122,107,0.10)', border: '1px solid rgba(255,122,107,0.30)',
          color: '#FF7A6B', textAlign: 'center',
        }}>{error}</div>
      )}
      <button onClick={handleUnlock} disabled={!picked} className="dd-btn dd-btn-primary" style={{
        height: 50, borderRadius: 16, fontSize: 15.5,
        opacity: picked ? 1 : 0.5, cursor: picked ? 'pointer' : 'not-allowed',
      }}>
        {!picked
          ? 'Pick a profile'
          : (addPwOpen && !needsPassword
              ? `Set password & continue as ${state.users.find(u => u.id === picked).displayName} →`
              : `Continue as ${state.users.find(u => u.id === picked).displayName} →`)}
      </button>
      <div className="dd-glass" style={{
        padding: '10px 12px', borderRadius: 12, fontSize: 11, color: 'var(--ink-3)',
        textAlign: 'center', lineHeight: 1.45,
      }}>
        Casual access protection only. The password is stored on this device in plain text and travels with backups.
      </div>
    </div>
  );
}

function WelcomeScreen({ nav }) {
  const { state } = useDiveDex();
  const expedition = state.expeditions.find(e => e.id === state.activeExpeditionId) || state.expeditions[0];
  const heroCreature = expedition && expedition.hero ? expedition.hero : 'scalloped-hammerhead';
  const heroName = (state.creatures.find(c => c.id === heroCreature) || {}).commonName || expedition.target || 'shark';
  return (
    <div className="dd-screen" style={{ padding: '78px 22px 110px', display: 'flex', flexDirection: 'column' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <Logo size={32}/>
        <button onClick={() => nav('dashboard')} className="dd-btn" style={{ background: 'transparent', color: 'var(--ink-3)', fontSize: 13, fontWeight: 600 }}>Skip ›</button>
      </div>
      <div style={{ display: 'grid', placeItems: 'center', marginTop: 18, marginBottom: 22 }}>
        <div style={{ position: 'relative', width: 260, height: 260, display: 'grid', placeItems: 'center' }}>
          <SonarRing size={250}/>
          {/* Silhouette sized to sit inside the sonar circle without the
              dorsal fin or cephalofoil clipping past the ring. */}
          <div style={{ position:'absolute', inset:0, display:'grid', placeItems:'center' }}>
            <CrSilhouette id={heroCreature} color="#82ECFF" size={110}/>
          </div>
        </div>
      </div>
      <h1 className="dd-h1" style={{ fontSize: 36, lineHeight: 1.02, margin: 0 }}>
        Turn every dive trip<br/>into a <span className="dd-shimmer" style={{ fontStyle: 'italic' }}>level.</span>
      </h1>
      <p style={{ marginTop: 14, color: 'var(--ink-2)', fontSize: 14.5, lineHeight: 1.5 }}>
        Log dives. Spot creatures. Level up.<br/>
        First trip: hunt for the <span style={{ color: '#82ECFF', fontWeight: 600 }}>{heroName}</span>.
      </p>

      <div style={{ marginTop: 18, display: 'flex', flexDirection: 'column', gap: 10 }}>
        <button onClick={() => nav('dashboard')} className="dd-btn dd-btn-primary" style={{ height: 52, borderRadius: 16, fontSize: 15.5 }}>
          Begin {expedition.title} →
        </button>
        <button onClick={() => nav('levels')} className="dd-btn dd-btn-ghost" style={{ height: 44, borderRadius: 14, fontSize: 13 }}>See all expeditions</button>
      </div>

      <div className="dd-glass" style={{ marginTop: 18, padding: '12px 14px', display: 'flex', alignItems: 'center', gap: 12, borderRadius: 14 }}>
        <div style={{ width: 32, height: 32, borderRadius: 10, background: 'rgba(255,122,107,0.15)', border: '1px solid rgba(255,122,107,0.35)', display: 'grid', placeItems: 'center', color: '#FF7A6B', fontWeight: 700 }}>!</div>
        <div style={{ flex: 1, fontSize: 12, color: 'var(--ink-2)', lineHeight: 1.4 }}>
          Log <strong style={{ color: 'var(--ink)' }}>after surfacing</strong>. Never use during a dive.
        </div>
      </div>
    </div>
  );
}

function LevelsScreen({ nav }) {
  const { state } = useDiveDex();
  const expeditions = state.expeditions;
  const activeId = state.activeExpeditionId;

  // Compute progress per expedition from state. Active expedition uses real
  // dives + discovered creatures filtered by region; locked/upcoming ones
  // show their target species + dates only.
  const progressFor = (exp) => {
    const regional = state.creatures.filter(c => (c.regions || []).indexOf(exp.region) >= 0);
    const speciesFound = regional.filter(c => c.discovered).length;
    const speciesTotal = regional.length || exp.speciesTotal || 0;
    const divesDone = exp.id === activeId ? state.dives.length : 0;
    const divesTotal = exp.diveTotal || 4;
    // XP attributable to this expedition — for now, only the active one earns
    // dive XP, so allocate teamXp to it. Locked expeditions show 0.
    const xpEarned = exp.id === activeId ? state.xp.teamXp : 0;
    return { speciesFound, speciesTotal, divesDone, divesTotal, xpEarned };
  };

  const active = expeditions.find(e => e.id === activeId) || expeditions[0];
  const upcoming = expeditions.filter(e => e.id !== activeId);

  // All-time totals across every expedition
  const allRegional = expeditions.reduce((acc, exp) => {
    state.creatures.forEach(c => {
      if ((c.regions || []).indexOf(exp.region) >= 0) acc.add(c.id);
    });
    return acc;
  }, new Set());
  const allTotalSpecies = allRegional.size;
  const allDiscovered = state.creatures.filter(c => c.discovered && allRegional.has(c.id)).length;
  const allDiveCap = expeditions.reduce((sum, e) => sum + (e.diveTotal || 0), 0);
  const allDives = state.dives.length;

  return (
    <div className="dd-screen" style={{ paddingBottom: 110 }}>
      <TopBar title="Levels" sub={`${expeditions.length} expeditions`} left={<Logo size={22} word={false}/>} right={<IconBtn>＋</IconBtn>}/>
      <div style={{ padding: '4px 18px' }}>
        <div className="dd-h-section" style={{ marginTop: 6, marginBottom: 10 }}>Active</div>
        <LevelCard level={active} index={expeditions.indexOf(active)} progress={progressFor(active)} primary onClick={() => nav('dashboard')}/>
        {upcoming.length > 0 && <>
          <div className="dd-h-section" style={{ marginTop: 22, marginBottom: 10 }}>Upcoming</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {upcoming.map(l => <LevelCard key={l.id} level={l} index={expeditions.indexOf(l)} progress={progressFor(l)}/>)}
          </div>
        </>}
        <div style={{ marginTop: 22, marginBottom: 8, display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
          <div className="dd-h-section">All-time totals</div>
          <div style={{ fontSize: 11, color: 'var(--ink-3)' }}>across all levels</div>
        </div>
        <GlassCard style={{ padding: '14px 16px', borderRadius: 18 }}>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 14 }}>
            <StatCell label="Dives" value={allDives} unit={`of ${allDiveCap}`}/>
            <StatCell label="Species" value={allDiscovered} unit={`/ ${allTotalSpecies}`} color="#63FFD2"/>
            <StatCell label="Team XP" value={state.xp.teamXp.toLocaleString()} color="#D7B46A"/>
          </div>
        </GlassCard>
      </div>
    </div>
  );
}

function LevelCard({ level, index, progress, primary, onClick }) {
  const locked = level.state === 'locked';
  const accent = level.accent || '#31D7FF';
  return (
    <div onClick={onClick} className="dd-glass" style={{
      position: 'relative', overflow: 'hidden', cursor: onClick ? 'pointer' : 'default',
      borderRadius: 22, padding: 0, opacity: locked ? 0.78 : 1,
      ...(primary ? { background: `linear-gradient(160deg, rgba(49,215,255,0.18), rgba(11,42,68,0.6) 50%, rgba(5,11,20,0.6))`, borderColor: 'rgba(49,215,255,0.32)' } : {}),
    }}>
      {primary && <div style={{ position: 'absolute', top: -40, right: -40, width: 200, height: 200, background: 'radial-gradient(circle, rgba(49,215,255,0.35), transparent 60%)' }}/>}
      {locked && <div style={{ position: 'absolute', top: 12, right: 12, padding: '4px 8px', borderRadius: 8, background: 'rgba(0,0,0,0.5)', border: '1px solid rgba(255,255,255,0.08)', fontSize: 10.5, color: 'var(--ink-3)', fontWeight: 600, letterSpacing: '0.08em' }}>LOCKED</div>}
      <div style={{ position: 'relative', padding: '16px 16px 14px', display: 'flex', gap: 14 }}>
        <div style={{ width: 64, height: 64, flex: '0 0 64px', borderRadius: 16, background: `linear-gradient(160deg, ${accent}33, rgba(11,42,68,0.6))`, border: `1px solid ${accent}44`, display: 'grid', placeItems: 'center', fontSize: 30 }}>{level.emoji}</div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 2 }}>
            <span style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: '0.1em', color: accent }}>LEVEL {(typeof index === 'number' ? index : 0) + 1}</span>
            {primary && <Chip variant="aqua" style={{ padding: '2px 7px', fontSize: 10 }}>Active</Chip>}
          </div>
          <div className="dd-h2" style={{ fontSize: 17, lineHeight: 1.15 }}>{level.title}</div>
          <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginTop: 4 }}>{level.sub} · {level.date}</div>
          {primary && progress && (
            <div style={{ marginTop: 10 }}>
              <XPBar value={progress.divesDone} max={Math.max(1, progress.divesTotal)} color="#31D7FF" height={6} label={null} animate={false}/>
              <div style={{ display: 'flex', gap: 14, marginTop: 8, fontSize: 11.5, color: 'var(--ink-2)', flexWrap: 'wrap' }}>
                <span><span className="dd-num" style={{ color: '#fff', fontWeight: 600 }}>{progress.divesDone}/{progress.divesTotal}</span> dives</span>
                <span><span className="dd-num" style={{ color: '#63FFD2', fontWeight: 600 }}>{progress.speciesFound}/{progress.speciesTotal}</span> species</span>
                <span><span className="dd-num" style={{ color: '#D7B46A', fontWeight: 600 }}>{progress.xpEarned.toLocaleString()}</span> XP</span>
              </div>
            </div>
          )}
          {!primary && (
            <div style={{ marginTop: 8, display: 'flex', alignItems: 'center', gap: 8 }}>
              <Chip variant="default" style={{ fontSize: 10.5 }}>Target · {level.target}</Chip>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

function DashboardScreen({ nav }) {
  const { state } = useDiveDex();
  const expedition = state.expeditions.find(e => e.id === state.activeExpeditionId) || state.expeditions[0];
  const rick = state.users.find(u => u.id === 'rick');
  const siet = state.users.find(u => u.id === 'siet');

  // Expedition music is owned by App (so it survives screen navigation) —
  // see the music effect in app.jsx. Nothing to do here.
  const region = expedition ? expedition.region : null;
  const regionalCreatures = region
    ? state.creatures.filter(c => (c.regions || []).indexOf(region) >= 0)
    : state.creatures;
  const speciesFound = regionalCreatures.filter(c => c.discovered).length;
  const speciesTotal = regionalCreatures.length || expedition.speciesTotal || 0;
  const divesDone = state.dives.length;
  const divesTotal = expedition.diveTotal || 4;
  const badgesUnlocked = state.unlockedAchievementIds.length;
  const teamXp = state.xp.teamXp;
  const nextTitleAt = 8000; // placeholder until ranks system lands
  const pearls = state.currencies.pearls;

  // Backup reminder — surfaced when local progress exists and no recent backup.
  const showBackupNudge = (divesDone > 0 || badgesUnlocked > 0)
    && window.DD_BACKUP && window.DD_BACKUP.isBackupStale(state.lastBackupAt);

  return (
    <div className="dd-screen" style={{ paddingBottom: 110 }}>
      <TopBar
        title={expedition.title}
        sub={`Level 1 · ${divesDone}/${divesTotal} dives`}
        left={<IconBtn onClick={() => nav('levels')}>‹</IconBtn>}
        right={<IconBtn onClick={() => nav('settings')}>⌥</IconBtn>}
      />
      <div style={{ padding: '6px 18px' }}>

        {/* Hero progression */}
        <GlassCard style={{ padding: 16, borderRadius: 22, position: 'relative', overflow: 'hidden', background: 'linear-gradient(160deg, rgba(49,215,255,0.14), rgba(11,42,68,0.5) 50%, rgba(5,11,20,0.6))', borderColor: 'rgba(49,215,255,0.25)' }}>
          <div style={{ position: 'absolute', top: -30, right: -20, opacity: 0.5 }}>
            <CrSilhouette id="scalloped-hammerhead" color="#31D7FF" size={150} glow={false}/>
          </div>
          <div style={{ position: 'relative' }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 8 }}>
              <div>
                <div className="dd-h-section" style={{ color: '#82ECFF', letterSpacing: '0.18em' }}>TEAM XP</div>
                <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginTop: 2 }}>
                  <span className="dd-num" style={{ fontSize: 40, fontWeight: 700, color: '#fff', letterSpacing: '-0.03em', textShadow: '0 0 14px rgba(49,215,255,0.4)' }}>{teamXp.toLocaleString()}</span>
                </div>
              </div>
              <PearlBalanceChip amount={pearls}/>
            </div>
            <div style={{ marginTop: 12 }}>
              <XPBar value={Math.min(teamXp, nextTitleAt)} max={nextTitleAt} color="#82ECFF" height={8} label="Rank: Luxury Shark Expedition Duo" sub={`${Math.max(0, nextTitleAt - teamXp).toLocaleString()} to next title`}/>
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginTop: 14 }}>
              <RickSietRow person={{ name: rick.displayName, color: rick.color, xp: state.xp.userXp.rick }}/>
              <RickSietRow person={{ name: siet.displayName, color: siet.color, xp: state.xp.userXp.siet }}/>
            </div>
          </div>
        </GlassCard>

        {showBackupNudge && (
          <div onClick={() => nav('settings')} className="dd-glass" style={{
            marginTop: 10, padding: '10px 12px', borderRadius: 12, cursor: 'pointer',
            background: 'linear-gradient(160deg, rgba(99,255,210,0.08), rgba(11,42,68,0.4))',
            borderColor: 'rgba(99,255,210,0.22)', display: 'flex', alignItems: 'center', gap: 10,
          }}>
            <div style={{ width: 22, height: 22, borderRadius: 8, background: 'rgba(99,255,210,0.18)', display: 'grid', placeItems: 'center', color: '#63FFD2', fontSize: 12, fontWeight: 700 }}>↑</div>
            <div style={{ flex: 1, fontSize: 11.5, color: 'var(--ink-2)', lineHeight: 1.4 }}>
              Progress saved locally. Export a backup if you want to keep it safe.
            </div>
            <span style={{ color: '#63FFD2', fontSize: 11, fontWeight: 700 }}>›</span>
          </div>
        )}

        {/* Quick stats — tappable. Dives → most recent recap, Species → Dex,
            Badges → Achievements. */}
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8, marginTop: 12 }}>
          <QuickStat icon="🤿" value={`${divesDone}/${divesTotal}`} label="Dives" color="#31D7FF"
            onClick={divesDone > 0 ? () => nav('recap', state.dives[state.dives.length - 1].id) : () => nav('log')}/>
          <QuickStat icon="🐠" value={`${speciesFound}/${speciesTotal}`} label="Species" color="#63FFD2"
            onClick={() => nav('dex')}/>
          <QuickStat icon="✦" value={`${badgesUnlocked}`} label="Badges" color="#D7B46A"
            onClick={() => nav('achievements')}/>
        </div>

        {/* Next mission — derive from real state */}
        {(() => {
          const lastDive = state.dives.length ? state.dives[state.dives.length - 1] : null;
          const tripFull = state.dives.length >= divesTotal;
          if (tripFull) {
            return (
              <>
                <div style={{ marginTop: 18, display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 8 }}>
                  <div className="dd-h-section">Trip complete</div>
                </div>
                <GlassCard style={{ padding: '14px 16px', borderRadius: 20, background: 'linear-gradient(140deg, rgba(99,255,210,0.14), rgba(11,42,68,0.4) 60%)', borderColor: 'rgba(99,255,210,0.3)' }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
                    <div style={{ width: 48, height: 48, borderRadius: 14, background: 'radial-gradient(circle at 30% 30%, #63FFD2, #1c5c4a)', display: 'grid', placeItems: 'center', fontSize: 22 }}>✓</div>
                    <div style={{ flex: 1 }}>
                      <div style={{ fontSize: 10.5, fontWeight: 700, color: '#63FFD2', letterSpacing: '0.12em' }}>EXPEDITION DONE</div>
                      <div className="dd-h2" style={{ fontSize: 16, marginTop: 1 }}>{state.dives.length} of {divesTotal} dives logged</div>
                      <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginTop: 2 }}>Tap a dive in the trip plan below for its recap.</div>
                    </div>
                  </div>
                </GlassCard>
              </>
            );
          }
          const nextNo = state.dives.length + 1;
          const carryOverSite = lastDive && lastDive.site ? lastDive.site : (expedition.target ? `Hunt: ${expedition.target}` : 'Upcoming dive');
          const carryOverOp = lastDive && lastDive.op ? lastDive.op : (expedition.sub || 'Trip plan');
          const carryOverGas = lastDive && lastDive.gas ? lastDive.gas : 'Nitrox 32';
          return (
            <>
              <div style={{ marginTop: 18, display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 8 }}>
                <div className="dd-h-section">Next mission</div>
                <span style={{ fontSize: 11, color: 'var(--ink-3)' }}>Dive {nextNo} of {divesTotal}</span>
              </div>
              <GlassCard onClick={() => lastDive ? nav('log-similar', lastDive.id) : nav('log')} style={{ padding: 0, borderRadius: 20, overflow: 'hidden', cursor: 'pointer', background: 'linear-gradient(140deg, rgba(215,180,106,0.16), rgba(11,42,68,0.4) 60%)', borderColor: 'rgba(215,180,106,0.3)' }}>
                <div style={{ padding: '14px 16px', display: 'flex', alignItems: 'center', gap: 12 }}>
                  <div style={{ width: 48, height: 48, flex: '0 0 48px', borderRadius: 14, background: 'radial-gradient(circle at 30% 30%, #D7B46A, #6b5530)', display: 'grid', placeItems: 'center', fontSize: 22, boxShadow: '0 0 18px rgba(215,180,106,0.4)' }}>🦈</div>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 10.5, fontWeight: 700, color: '#D7B46A', letterSpacing: '0.12em' }}>{lastDive ? 'LOG NEXT DIVE' : 'LOG FIRST DIVE'}</div>
                    <div className="dd-h2" style={{ fontSize: 16, marginTop: 1, lineHeight: 1.15, wordBreak: 'break-word' }}>{lastDive ? `Dive ${nextNo} · ${carryOverSite}` : `Begin ${expedition.title}`}</div>
                    <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{lastDive ? `${carryOverOp} · ${carryOverGas}` : `Target · ${expedition.target}`}</div>
                  </div>
                  <div style={{ color: '#D7B46A', fontSize: 18, flex: '0 0 auto' }}>›</div>
                </div>
                <div style={{ padding: '0 16px 14px', display: 'flex', gap: 6, flexWrap: 'wrap' }}>
                  {expedition.target && <Chip variant="gold" icon="⊥">Target · {expedition.target}</Chip>}
                  {lastDive ? <Chip variant="aqua">Tap to log similar</Chip> : <Chip variant="aqua">Tap to log</Chip>}
                  {lastDive && lastDive.gas && <Chip>{lastDive.gas} {lastDive.nitrox || ''}</Chip>}
                </div>
              </GlassCard>
            </>
          );
        })()}

        {/* Status chips — derived from real side-quest + Air Zen state */}
        <div style={{ marginTop: 18, marginBottom: 8 }}><div className="dd-h-section">Status</div></div>
        <div style={{ display: 'flex', gap: 7, flexWrap: 'wrap' }}>
          {(() => {
            const completedToday = state.sideQuests.filter(q => q.state === 'completed').length;
            const activeQuests = state.sideQuests.filter(q => q.state === 'active');
            const lockedQuests = state.sideQuests.filter(q => q.state === 'locked');
            // Air Zen streak — count consecutive most-recent dives that earned the bonus
            let zenStreak = 0;
            for (let i = state.dives.length - 1; i >= 0; i--) {
              if (state.dives[i].airZen) zenStreak++; else break;
            }
            const chips = [];
            if (activeQuests.length > 0) chips.push(
              <Chip key="active" variant="aqua" icon="◉">{activeQuests.length} active side quest{activeQuests.length === 1 ? '' : 's'}</Chip>
            );
            if (completedToday > 0) chips.push(
              <Chip key="done" variant="foam" icon="◯">{completedToday} side quest{completedToday === 1 ? '' : 's'} done</Chip>
            );
            if (zenStreak > 0) chips.push(
              <Chip key="zen" variant="foam" icon="☉">Air Zen streak ×{zenStreak}</Chip>
            );
            if (lockedQuests.length > 0) chips.push(
              <Chip key="locked" icon="◬">{lockedQuests.length} locked</Chip>
            );
            if (chips.length === 0) chips.push(
              <Chip key="empty">No status yet — log a dive or complete a side quest</Chip>
            );
            return chips;
          })()}
        </div>

        {/* Trip plan */}
        <div style={{ marginTop: 18, display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 8 }}>
          <div className="dd-h-section">Trip plan</div>
          <button onClick={() => nav('log')} className="dd-btn" style={{ background:'transparent', color:'#82ECFF', fontSize: 12, fontWeight: 600 }}>Log next dive →</button>
        </div>
        <GlassCard style={{ padding: 0, borderRadius: 18 }}>
          {(() => {
            // Build trip plan: real logged dives + upcoming placeholders.
            const logged = state.dives.map(d => ({
              ...d, status: 'logged',
              date: d.createdAt ? new Date(d.createdAt).toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) : '',
              xp: d.totalXp || 0, sightings: (state.sightings.filter(s => s.diveId === d.id).length),
            }));
            const remaining = Math.max(0, divesTotal - logged.length);
            const upcoming = Array.from({ length: remaining }).map((_, i) => {
              const isNext = i === 0;
              return {
                id: 'plan-' + i,
                no: logged.length + i + 1,
                // Next dive gets the "Hunt" hint; later placeholders just
                // read "Dive N · —" so the trip plan doesn't show four
                // identical "Hunt: Hammerhead" rows. The em-dash is the
                // "TBD" placeholder for sites you haven't logged yet.
                site: isNext
                  ? (expedition.target ? `Hunt: ${expedition.target}` : 'Next dive')
                  : '—',
                op: isNext ? (expedition.sub || 'Trip plan') : 'Upcoming',
                date: '',
                status: isNext ? 'next' : 'locked',
              };
            });
            const rows = [...logged, ...upcoming];
            return rows.length === 0 ? (
              <div style={{ padding: 16, fontSize: 12, color: 'var(--ink-3)', textAlign: 'center' }}>
                No dives planned yet. Log your first dive to start the trip plan.
              </div>
            ) : rows.map((d, i) => (
              <DivePlanRow key={d.id} dive={d} last={i === rows.length - 1}
                onClick={() => {
                  if (d.status === 'locked') return;
                  if (d.status === 'logged') nav('recap', d.id);
                  else nav('log');
                }}/>
            ));
          })()}
        </GlassCard>

        {/* Side quests pull */}
        <div style={{ marginTop: 18, display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 8 }}>
          <div className="dd-h-section">Side quests</div>
          <button onClick={() => nav('sidequests')} className="dd-btn" style={{ background:'transparent', color:'#82ECFF', fontSize: 12, fontWeight: 600 }}>All ({state.sideQuests.length}) →</button>
        </div>
        <div style={{ display: 'flex', gap: 10, overflowX: 'auto', scrollbarWidth: 'none', margin: '0 -18px', padding: '0 18px' }}>
          {state.sideQuests.slice(0, 4).map(sq => (
            <div key={sq.id} onClick={() => nav('sidequests')} className="dd-glass" style={{ flex: '0 0 150px', padding: 12, borderRadius: 14, cursor: 'pointer' }}>
              <div style={{ fontSize: 22 }}>{sq.badge}</div>
              <div style={{ fontSize: 12.5, fontWeight: 700, marginTop: 6, color: '#EAF5FF', lineHeight: 1.2 }}>{sq.title}</div>
              <div style={{ fontSize: 10.5, color: 'var(--ink-3)', marginTop: 4 }}>{sq.sub}</div>
              <div style={{ marginTop: 8, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                <Chip style={{ fontSize: 10, padding: '3px 7px' }} variant={sq.state === 'completed' ? 'foam' : sq.state === 'active' ? 'aqua' : 'default'}>
                  {sq.state === 'completed' ? '✓ Done' : sq.state === 'active' ? '· Active' : 'Locked'}
                </Chip>
                <span className="dd-num" style={{ fontSize: 10.5, color: '#D7B46A', fontWeight: 600 }}>+{sq.rewardXp || sq.xp || 0}</span>
              </div>
            </div>
          ))}
        </div>

        {/* Safety reminder */}
        <div className="dd-glass" style={{ marginTop: 18, padding: '12px 14px', display: 'flex', alignItems: 'center', gap: 10, borderRadius: 14, borderColor: 'rgba(255,122,107,0.18)' }}>
          <div style={{ width: 26, height: 26, borderRadius: 8, background: 'rgba(255,122,107,0.15)', display: 'grid', placeItems: 'center', color: '#FF7A6B', fontWeight: 700, fontSize: 12 }}>!</div>
          <div style={{ flex: 1, fontSize: 11.5, color: 'var(--ink-2)' }}>Post-dive logbook only. Not a dive computer.</div>
        </div>

      </div>
    </div>
  );
}

function RickSietRow({ person }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 9 }}>
      <Avatar name={person.name} color={person.color} size={32}/>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 11, color: 'var(--ink-3)', fontWeight: 600 }}>{person.name}</div>
        <div className="dd-num" style={{ fontSize: 15, color: '#fff', fontWeight: 600 }}>{person.xp.toLocaleString()}</div>
      </div>
    </div>
  );
}

function QuickStat({ icon, value, label, color, onClick }) {
  return (
    <div onClick={onClick} className="dd-glass" style={{
      padding: '12px 12px', borderRadius: 14, textAlign: 'left',
      cursor: onClick ? 'pointer' : 'default',
    }}>
      <div style={{ fontSize: 16, color }}>{icon}</div>
      <div className="dd-num" style={{ fontSize: 18, fontWeight: 700, color: '#fff', marginTop: 4 }}>{value}</div>
      <div style={{ fontSize: 10.5, color: 'var(--ink-3)', marginTop: 1, fontWeight: 600, letterSpacing: '0.05em', display: 'flex', alignItems: 'center', gap: 4 }}>
        {label}
        {onClick && <span style={{ color: 'var(--ink-3)', opacity: 0.6, fontSize: 10 }}>›</span>}
      </div>
    </div>
  );
}

function DivePlanRow({ dive, last, onClick }) {
  const isLogged = dive.status === 'logged';
  const isNext = dive.status === 'next';
  const isLocked = dive.status === 'locked';
  return (
    <div onClick={onClick} style={{
      display: 'flex', alignItems: 'center', gap: 12, padding: '12px 14px',
      borderBottom: last ? 'none' : '1px solid rgba(130,236,255,0.06)',
      cursor: !isLocked ? 'pointer' : 'default', opacity: isLocked ? 0.5 : 1,
    }}>
      <div style={{
        width: 32, height: 32, borderRadius: 10, display: 'grid', placeItems: 'center',
        background: isLogged ? 'rgba(99,255,210,0.15)' : isNext ? 'rgba(49,215,255,0.18)' : 'rgba(255,255,255,0.04)',
        border: `1px solid ${isLogged ? 'rgba(99,255,210,0.4)' : isNext ? 'rgba(49,215,255,0.4)' : 'rgba(255,255,255,0.08)'}`,
        color: isLogged ? '#63FFD2' : isNext ? '#31D7FF' : 'var(--ink-3)',
        fontFamily: 'JetBrains Mono', fontWeight: 600, fontSize: 13,
      }}>{isLogged ? '✓' : dive.no}</div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 13.5, fontWeight: 600, color: '#fff' }}>Dive {dive.no} · {dive.site}</div>
        <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 1 }}>{dive.date} {isLogged && `· ${dive.maxDepth}m / ${dive.time}min`}</div>
      </div>
      {isLogged && <span className="dd-num" style={{ fontSize: 12, color: '#D7B46A', fontWeight: 600 }}>+{dive.xp}</span>}
      {isNext && <Chip variant="aqua" style={{ fontSize: 10 }}>Next →</Chip>}
    </div>
  );
}

function LogDiveScreen({ nav, editDiveId, templateDiveId }) {
  const { state, actions } = useDiveDex();
  const editing = !!editDiveId;
  const templating = !editing && !!templateDiveId;
  const existingDive = editing ? state.dives.find(d => d.id === editDiveId) : null;
  const templateDive = templating ? state.dives.find(d => d.id === templateDiveId) : null;
  const existingSightings = editing && existingDive
    ? state.sightings
        .filter(s => s.diveId === existingDive.id)
        .map(s => ({ creatureId: s.creatureId, count: s.count, firstSpotter: s.firstSpotter }))
    : null;

  const [step, setStep] = useState(0);
  const [form, setForm] = useState(() => {
    if (existingDive) {
      return {
        loggedAt: isoToDatetimeLocal(existingDive.createdAt) || isoToDatetimeLocal(new Date().toISOString()),
        site: existingDive.site || '', op: existingDive.op || '', buddy: existingDive.buddy || 'Rick & Siet',
        maxDepth: existingDive.maxDepth || 0, time: existingDive.time || 0,
        temp: existingDive.temp || 0, vis: existingDive.vis || 0,
        gas: existingDive.gas || 'Air', nitrox: existingDive.nitrox || 21,
        startBar: existingDive.startBar || 200, endBar: existingDive.endBar || 50,
        mood: existingDive.mood || 'zen', current: existingDive.current || 'mild',
        notes: existingDive.notes || '',
        quote: existingDive.quote || '',
        buddyCheck: existingDive.buddyCheck !== false,
        photos: existingDive.photos || existingDive.photoCount || 0,
      };
    }
    if (templateDive) {
      // Carry the site/op/buddy/conditions over, reset everything dive-specific
      // (pressures, notes, photos) so the user fills the new-dive details.
      // Default loggedAt to NOW (template is for "another dive today" not a
      // copy of the original timestamp).
      return {
        loggedAt: isoToDatetimeLocal(new Date().toISOString()),
        site: templateDive.site || '', op: templateDive.op || '', buddy: templateDive.buddy || 'Rick & Siet',
        maxDepth: templateDive.maxDepth || 0, time: templateDive.time || 0,
        temp: templateDive.temp || 0, vis: templateDive.vis || 0,
        gas: templateDive.gas || 'Air', nitrox: templateDive.nitrox || 21,
        startBar: 200, endBar: 50,
        mood: 'zen', current: templateDive.current || 'mild',
        notes: '', quote: '',
        buddyCheck: true, photos: 0,
      };
    }
    return {
      loggedAt: isoToDatetimeLocal(new Date().toISOString()),
      site: 'Mikomoto Hammers', op: 'Ito Diving Service', buddy: 'Rick & Siet',
      maxDepth: 24, time: 35, temp: 20, vis: 18, gas: 'Nitrox', nitrox: 32,
      startBar: 210, endBar: 65, mood: 'awe', current: 'strong', notes: '',
      quote: '',
      buddyCheck: true, photos: 3,
    };
  });
  const [sightings, setSightings] = useState(() => {
    if (existingSightings) return existingSightings;
    if (templating) return []; // Fresh sightings list — this is a new dive
    return [
      { creatureId: 'scalloped-hammerhead', count: 14, firstSpotter: 'siet' },
      { creatureId: 'eagle-ray', count: 3, firstSpotter: 'rick' },
      { creatureId: 'green-turtle', count: 1, firstSpotter: 'siet' },
    ];
  });
  const steps = ['Basics', 'Sightings', 'Vibe', 'Recap'];

  // When editing, pull the existing photos for this dive out of IndexedDB so
  // they show up in the attach field with delete affordances.
  React.useEffect(() => {
    if (!editing || !editDiveId || !window.DD_PHOTOS) return;
    window.DD_PHOTOS.getPhotosByDive(editDiveId).then(existing => {
      setForm(f => ({ ...f, _existingPhotos: existing }));
    }).catch(() => {});
  }, [editing, editDiveId]);

  const saveAndGo = async () => {
    // Convert the form's local-time picker value back to an ISO timestamp
    // for storage. Strip helper fields (loggedAt, _pendingPhotos,
    // _existingPhotos) so they don't leak into the dive record.
    const { loggedAt, _pendingPhotos, _existingPhotos, ...rest } = form;
    const isoLoggedAt = datetimeLocalToIso(loggedAt);
    // Total photo count for scoring: already-stored + freshly attached.
    const photoCount = ((_existingPhotos && _existingPhotos.length) || 0) + ((_pendingPhotos && _pendingPhotos.length) || 0);
    const payload = { ...rest, createdAt: isoLoggedAt, photos: photoCount };
    let diveId;
    if (editing) {
      actions.updateDive(editDiveId, payload, sightings);
      diveId = editDiveId;
    } else {
      diveId = actions.logDive(payload, sightings);
    }
    // Persist any pending photos against the dive id (don't block nav on it —
    // the Recap will pick them up reactively from IndexedDB on render).
    if (window.DD_PHOTOS && _pendingPhotos && _pendingPhotos.length > 0) {
      window.DD_PHOTOS.addPhotos(diveId, _pendingPhotos).catch(() => {});
    }
    nav('recap', diveId);
  };
  const next = () => step < steps.length - 1 ? setStep(step + 1) : saveAndGo();
  const back = () => step > 0 ? setStep(step - 1) : nav(editing ? 'recap' : 'dashboard', editing ? editDiveId : undefined);

  const onDelete = () => {
    if (!editing) return;
    // Build an impact preview: which creatures will be un-discovered because
    // this dive is the only place they've been spotted? (Pure read — same
    // logic as deleteDive's rollback in context.jsx.)
    const undiscoverNames = state.creatures
      .filter(c =>
        c.firstSeenDiveId === editDiveId &&
        !state.sightings.some(s => s.diveId !== editDiveId && s.creatureId === c.id)
      )
      .map(c => c.commonName);
    const undiscoverNote = undiscoverNames.length > 0
      ? `\n\nThese ${undiscoverNames.length === 1 ? 'species will be removed' : 'species will be removed'} from the Dex (only spotted on this dive):\n• ${undiscoverNames.join('\n• ')}`
      : '\n\nNo creatures will leave the Dex — every species you saw was also logged on another dive.';
    const xpLost = (existingDive && existingDive.totalXp) ? existingDive.totalXp : 0;
    const pearlsLost = (existingDive && existingDive.pearls) ? existingDive.pearls : 0;
    const ok = window.confirm(
      `Delete Dive ${existingDive ? existingDive.no : ''}?\n\n` +
      `−${xpLost.toLocaleString()} Team XP, −${pearlsLost} Pearls (clamped at 0). ` +
      `Achievements you unlocked stay yours.` +
      undiscoverNote
    );
    if (!ok) return;
    actions.deleteDive(editDiveId);
    nav('memories');
  };

  return (
    <div className="dd-screen" style={{ paddingBottom: 110 }}>
      <TopBar
        title={editing ? 'Edit dive' : (templating ? 'Log similar dive' : 'Log a dive')}
        sub={`Step ${step + 1} of ${steps.length} · ${steps[step]}${editing && existingDive ? ' · Dive ' + existingDive.no : ''}`}
        left={<IconBtn onClick={back}>‹</IconBtn>}
        right={<button onClick={saveAndGo} className="dd-btn" style={{ background:'transparent', color:'var(--ink-3)', fontSize:13, fontWeight: 600 }}>{editing ? 'Done' : 'Save'}</button>}
      />
      <div style={{ padding: '4px 18px' }}>
        {editing && (
          <div className="dd-glass" style={{ padding: '8px 12px', borderRadius: 10, fontSize: 11, color: 'var(--ink-3)', textAlign: 'center', marginBottom: 10, lineHeight: 1.4 }}>
            Editing this dive updates the record. XP, Pearls and badges stay as they were.
          </div>
        )}
        {templating && (
          <div className="dd-glass" style={{ padding: '8px 12px', borderRadius: 10, fontSize: 11, color: 'var(--ink-3)', textAlign: 'center', marginBottom: 10, lineHeight: 1.4 }}>
            Carried over from Dive {templateDive.no}: site, operator, buddy, gas, conditions. Pressures, sightings and notes are fresh.
          </div>
        )}
        <div style={{ display:'flex', justifyContent:'center', marginBottom: 16 }}><ProgressDots step={step} total={steps.length}/></div>
        {step === 0 && <LogStepBasics form={form} setForm={setForm}/>}
        {step === 1 && <SightingsBuilder picked={sightings} setPicked={setSightings}/>}
        {step === 2 && <LogStepVibe form={form} setForm={setForm} sightings={sightings}/>}
        {step === 3 && <LogStepRecapPreview form={form} sightings={sightings} editing={editing} onGo={saveAndGo}/>}
        <button onClick={next} className="dd-btn dd-btn-primary" style={{ height: 50, width: '100%', borderRadius: 14, marginTop: 20, fontSize: 14.5 }}>
          {step < steps.length - 1
            ? `Continue → ${steps[step + 1]}`
            : (editing ? 'Save edits →' : 'Save dive · see Recap →')}
        </button>
        {editing && (
          <button onClick={onDelete} className="dd-btn" style={{
            width: '100%', height: 40, marginTop: 14, borderRadius: 12, fontSize: 12, fontWeight: 700,
            background: 'rgba(255,122,107,0.08)', border: '1px solid rgba(255,122,107,0.28)', color: '#FF7A6B',
          }}>Delete this dive</button>
        )}
      </div>
    </div>
  );
}

function Field({ label, children, hint, style }) {
  return (
    <div style={{ marginBottom: 12, ...style }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 6 }}>
        <label style={{ fontSize: 10.5, color: 'var(--ink-3)', fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase' }}>{label}</label>
        {hint && <span style={{ fontSize: 10.5, color: 'var(--ink-3)' }}>{hint}</span>}
      </div>
      {children}
    </div>
  );
}

function TextIn({ value, onChange, suffix, mono, type = 'text' }) {
  return (
    <div className="dd-glass" style={{ padding: '10px 12px', borderRadius: 12, display: 'flex', alignItems: 'baseline', gap: 6 }}>
      <input type={type} value={value} onChange={e => onChange(type === 'number' ? Number(e.target.value) : e.target.value)} style={{
        background: 'transparent', border: 0, outline: 'none', color: '#fff',
        flex: 1, fontFamily: mono ? 'JetBrains Mono' : 'Manrope',
        fontSize: 15, fontWeight: 600, width: '100%', minWidth: 0,
      }}/>
      {suffix && <span style={{ fontSize: 12, color: 'var(--ink-3)' }}>{suffix}</span>}
    </div>
  );
}

// Horizontal scroll row of pickable chips. The currently-matching chip is
// highlighted. Pure suggestion layer — the underlying TextIn always wins.
function QuickPickRow({ items, value, onPick }) {
  return (
    <div style={{ display: 'flex', gap: 6, overflowX: 'auto', marginTop: 6, scrollbarWidth: 'none', padding: '2px 0' }}>
      {items.map(item => {
        const on = value === item;
        return (
          <button key={item} onClick={() => onPick(item)} className="dd-btn" style={{
            flex: '0 0 auto', padding: '5px 10px', borderRadius: 999, fontSize: 11, fontWeight: 600,
            whiteSpace: 'nowrap',
            background: on ? 'rgba(49,215,255,0.18)' : 'rgba(255,255,255,0.04)',
            color: on ? '#82ECFF' : 'var(--ink-2)',
            border: on ? '1px solid rgba(49,215,255,0.36)' : '1px solid rgba(130,236,255,0.12)',
          }}>{item}</button>
        );
      })}
    </div>
  );
}

function SegSwitch({ value, onChange, options }) {
  return (
    <div style={{ display:'flex', padding: 3, background:'rgba(255,255,255,0.04)', border:'1px solid rgba(130,236,255,0.10)', borderRadius: 12 }}>
      {options.map(o => (
        <button key={o.value} onClick={() => onChange(o.value)} className="dd-btn" style={{
          flex: 1, padding: '8px 10px', borderRadius: 10, fontSize: 12.5,
          background: value === o.value ? 'rgba(49,215,255,0.15)' : 'transparent',
          color: value === o.value ? '#82ECFF' : 'var(--ink-2)',
          border: value === o.value ? '1px solid rgba(49,215,255,0.32)' : '1px solid transparent',
        }}>{o.label}</button>
      ))}
    </div>
  );
}

function LogStepBasics({ form, setForm }) {
  const { state } = useDiveDex();
  const set = (k) => (v) => setForm({ ...form, [k]: v });
  // Pull regional sites + operators (plus any site the user has already used)
  // so the chips include both curated and history-derived options.
  const expedition = state.expeditions.find(e => e.id === state.activeExpeditionId);
  const region = expedition ? expedition.region : null;
  const curatedSites = (window.DD_STATE.SEED_DIVE_SITES || {})[region] || [];
  const historySites = Array.from(new Set(state.dives.map(d => d.site).filter(Boolean)));
  const allSites = Array.from(new Set([...historySites, ...curatedSites]));
  const curatedOps = (window.DD_STATE.SEED_DIVE_OPERATORS || {})[region] || [];
  const historyOps = Array.from(new Set(state.dives.map(d => d.op).filter(Boolean)));
  const allOps = Array.from(new Set([...historyOps, ...curatedOps]));
  return (
    <div>
      <Field label="Dive date & time" hint="local time">
        <div className="dd-glass" style={{ padding: '10px 12px', borderRadius: 12, display: 'flex', alignItems: 'center' }}>
          <input
            type="datetime-local"
            value={form.loggedAt || ''}
            onChange={e => set('loggedAt')(e.target.value)}
            style={{
              background: 'transparent', border: 0, outline: 'none', color: '#EAF5FF',
              flex: 1, fontFamily: 'Manrope', fontSize: 14, fontWeight: 600, width: '100%',
              colorScheme: 'dark',
            }}
          />
        </div>
        <div style={{ marginTop: 6, display: 'flex', gap: 6, flexWrap: 'wrap' }}>
          <button onClick={() => set('loggedAt')(isoToDatetimeLocal(new Date().toISOString()))} className="dd-btn" style={{
            padding: '4px 10px', borderRadius: 999, fontSize: 11, fontWeight: 600,
            background: 'rgba(255,255,255,0.04)', color: 'var(--ink-2)',
            border: '1px solid rgba(130,236,255,0.12)',
          }}>Now</button>
          <button onClick={() => {
            const d = new Date(); d.setHours(d.getHours() - 1);
            set('loggedAt')(isoToDatetimeLocal(d.toISOString()));
          }} className="dd-btn" style={{
            padding: '4px 10px', borderRadius: 999, fontSize: 11, fontWeight: 600,
            background: 'rgba(255,255,255,0.04)', color: 'var(--ink-2)',
            border: '1px solid rgba(130,236,255,0.12)',
          }}>1h ago</button>
          <button onClick={() => {
            const d = new Date(); d.setHours(d.getHours() - 3);
            set('loggedAt')(isoToDatetimeLocal(d.toISOString()));
          }} className="dd-btn" style={{
            padding: '4px 10px', borderRadius: 999, fontSize: 11, fontWeight: 600,
            background: 'rgba(255,255,255,0.04)', color: 'var(--ink-2)',
            border: '1px solid rgba(130,236,255,0.12)',
          }}>3h ago</button>
          <button onClick={() => {
            const d = new Date(); d.setDate(d.getDate() - 1); d.setHours(9, 0, 0, 0);
            set('loggedAt')(isoToDatetimeLocal(d.toISOString()));
          }} className="dd-btn" style={{
            padding: '4px 10px', borderRadius: 999, fontSize: 11, fontWeight: 600,
            background: 'rgba(255,255,255,0.04)', color: 'var(--ink-2)',
            border: '1px solid rgba(130,236,255,0.12)',
          }}>Yesterday 09:00</button>
        </div>
      </Field>
      <Field label="Dive site">
        <TextIn value={form.site} onChange={set('site')}/>
        {allSites.length > 0 && <QuickPickRow items={allSites} value={form.site} onPick={set('site')}/>}
      </Field>
      <Field label="Operator">
        <TextIn value={form.op} onChange={set('op')}/>
        {allOps.length > 0 && <QuickPickRow items={allOps} value={form.op} onPick={set('op')}/>}
      </Field>
      <Field label="Buddy team"><TextIn value={form.buddy} onChange={set('buddy')}/></Field>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
        <Field label="Max depth" hint="m"><TextIn value={form.maxDepth} onChange={set('maxDepth')} suffix="m" mono type="number"/></Field>
        <Field label="Dive time" hint="min"><TextIn value={form.time} onChange={set('time')} suffix="min" mono type="number"/></Field>
        <Field label="Water temp"><TextIn value={form.temp} onChange={set('temp')} suffix="°C" mono type="number"/></Field>
        <Field label="Visibility"><TextIn value={form.vis} onChange={set('vis')} suffix="m" mono type="number"/></Field>
      </div>
      <Field label="Gas mix">
        <SegSwitch value={form.gas} onChange={set('gas')} options={[{value:'Air', label:'Air'},{value:'Nitrox', label:'Nitrox 32'},{value:'Custom', label:'Custom'}]}/>
      </Field>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
        <Field label="Start pressure"><TextIn value={form.startBar} onChange={set('startBar')} suffix="bar" mono type="number"/></Field>
        <Field label="End pressure" hint="safe ≥ 50"><TextIn value={form.endBar} onChange={set('endBar')} suffix="bar" mono type="number"/></Field>
      </div>
      <Field label="Notes">
        <div className="dd-glass" style={{ padding: '10px 12px', borderRadius: 12 }}>
          <textarea value={form.notes} onChange={e => set('notes')(e.target.value)} placeholder="Was that a hammerhead or an underwater Boeing?" style={{ background: 'transparent', border: 0, outline: 'none', color: '#fff', width: '100%', minHeight: 56, resize: 'none', fontFamily: 'Manrope', fontSize: 13.5 }}/>
        </div>
      </Field>
      <Field label="Photos" hint="optional · stored on this device">
        <PhotoAttachField
          pending={form._pendingPhotos || []}
          existing={form._existingPhotos || []}
          onAdd={files => setForm({ ...form, _pendingPhotos: [...(form._pendingPhotos || []), ...files] })}
          onRemovePending={i => setForm({ ...form, _pendingPhotos: (form._pendingPhotos || []).filter((_, j) => j !== i) })}
          onRemoveExisting={id => {
            if (window.DD_PHOTOS) window.DD_PHOTOS.deletePhoto(id).catch(() => {});
            setForm({ ...form, _existingPhotos: (form._existingPhotos || []).filter(p => p.id !== id) });
          }}
        />
      </Field>
    </div>
  );
}

// Multi-photo file picker with thumbnail grid + remove. Splits the photos
// into "existing" (already in IndexedDB, removable via DD_PHOTOS.deletePhoto)
// and "pending" (File objects from this session, removed in-memory). Object
// URLs are revoked on unmount to avoid blob leaks.
function PhotoAttachField({ pending, existing, onAdd, onRemovePending, onRemoveExisting }) {
  const fileRef = React.useRef(null);
  const pendingUrls = React.useMemo(() => pending.map(f => URL.createObjectURL(f)), [pending]);
  React.useEffect(() => () => pendingUrls.forEach(u => URL.revokeObjectURL(u)), [pendingUrls]);
  const existingUrls = React.useMemo(() => existing.map(p => URL.createObjectURL(p.blob)), [existing]);
  React.useEffect(() => () => existingUrls.forEach(u => URL.revokeObjectURL(u)), [existingUrls]);
  const total = pending.length + existing.length;
  return (
    <div className="dd-glass" style={{ padding: 12, borderRadius: 14 }}>
      <button onClick={() => fileRef.current && fileRef.current.click()} className="dd-btn dd-btn-ghost" style={{
        height: 40, width: '100%', borderRadius: 12, fontSize: 13, fontWeight: 700,
      }}>＋  Add photos</button>
      <input ref={fileRef} type="file" accept="image/*" multiple
             onChange={e => { onAdd(Array.from(e.target.files || [])); e.target.value = ''; }}
             style={{ display: 'none' }}/>
      {total > 0 && (
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 6, marginTop: 10 }}>
          {existing.map((p, i) => (
            <div key={p.id} style={{ position: 'relative', aspectRatio: '1 / 1', borderRadius: 10, overflow: 'hidden', background: 'rgba(0,0,0,0.3)', border: '1px solid rgba(130,236,255,0.10)' }}>
              <img src={existingUrls[i]} style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}/>
              <button onClick={() => onRemoveExisting(p.id)} className="dd-btn" style={{
                position: 'absolute', top: 4, right: 4, width: 22, height: 22, padding: 0, borderRadius: 11,
                background: 'rgba(0,0,0,0.65)', color: '#FF7A6B', fontSize: 13, lineHeight: 1, fontWeight: 700,
                border: '1px solid rgba(255,122,107,0.36)',
              }}>×</button>
            </div>
          ))}
          {pending.map((f, i) => (
            <div key={'p-' + i} style={{ position: 'relative', aspectRatio: '1 / 1', borderRadius: 10, overflow: 'hidden', background: 'rgba(0,0,0,0.3)', border: '1px solid rgba(99,255,210,0.22)' }}>
              <img src={pendingUrls[i]} style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}/>
              <div style={{ position: 'absolute', left: 4, bottom: 4, padding: '2px 5px', borderRadius: 6, background: 'rgba(99,255,210,0.18)', border: '1px solid rgba(99,255,210,0.36)', color: '#63FFD2', fontSize: 8.5, fontWeight: 700, letterSpacing: '0.06em' }}>NEW</div>
              <button onClick={() => onRemovePending(i)} className="dd-btn" style={{
                position: 'absolute', top: 4, right: 4, width: 22, height: 22, padding: 0, borderRadius: 11,
                background: 'rgba(0,0,0,0.65)', color: '#FF7A6B', fontSize: 13, lineHeight: 1, fontWeight: 700,
                border: '1px solid rgba(255,122,107,0.36)',
              }}>×</button>
            </div>
          ))}
        </div>
      )}
      <div style={{ fontSize: 10.5, color: 'var(--ink-3)', marginTop: 8, lineHeight: 1.4 }}>
        {total} {total === 1 ? 'photo' : 'photos'} · resized to 1600px and stored locally on this device.
      </div>
    </div>
  );
}

function LogStepImport() {
  return (
    <div>
      <div className="dd-glass" style={{ padding: 16, borderRadius: 16, textAlign: 'center', borderStyle: 'dashed' }}>
        <div style={{ fontSize: 32, marginBottom: 6 }}>📥</div>
        <div className="dd-h2" style={{ fontSize: 15 }}>Import a Suunto .FIT file</div>
        <div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 4 }}>Drag in or pick from Files</div>
        <button className="dd-btn dd-btn-ghost" style={{ marginTop: 12, height: 38, padding: '0 18px', borderRadius: 12, fontSize: 12.5 }}>Choose file…</button>
      </div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, margin: '14px 0', color: 'var(--ink-3)', fontSize: 11 }}>
        <div style={{ flex: 1, height: 1, background: 'rgba(130,236,255,0.10)' }}/>OR<div style={{ flex: 1, height: 1, background: 'rgba(130,236,255,0.10)' }}/>
      </div>
      <GlassCard style={{ borderRadius: 14, padding: 14 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <div>
            <div style={{ fontSize: 13.5, fontWeight: 600 }}>Continue with manual entry</div>
            <div style={{ fontSize: 11, color: 'var(--ink-3)', marginTop: 2 }}>You can also import later from settings.</div>
          </div>
          <span style={{ color: 'var(--ink-3)' }}>›</span>
        </div>
      </GlassCard>
      <div className="dd-glass" style={{ marginTop: 10, padding: 12, borderRadius: 12, fontSize: 11.5, color: 'var(--ink-3)' }}>
        Recent imports · <span style={{ color: 'var(--ink-2)' }}>Shark Scramble — 6 May 14:22.fit</span>
      </div>
    </div>
  );
}

function SightingsBuilder({ picked, setPicked }) {
  const { state } = useDiveDex();
  const expedition = state.expeditions.find(e => e.id === state.activeExpeditionId);
  const region = expedition ? expedition.region : null;

  const lookup = (id) => {
    const fromState = state.creatures.find(c => c.id === id);
    if (fromState) return { name: fromState.commonName, rarity: fromState.rarity, familyType: fromState.familyType };
    const fromLegacy = (window.CREATURES || []).find(c => c.id === id);
    if (fromLegacy) return { name: fromLegacy.name, rarity: fromLegacy.rarity, familyType: 'reefFish' };
    return { name: id, rarity: 'common', familyType: 'reefFish' };
  };

  // Editor actions
  const removeAt = (i) => setPicked(picked.filter((_, j) => j !== i));
  const incCount = (i, delta) => setPicked(picked.map((p, j) =>
    j === i ? { ...p, count: Math.max(1, (p.count || 1) + delta) } : p));
  const setCount = (i, raw) => {
    // Allow empty string while editing; clamp on commit via blur in the input.
    const n = Math.max(1, Math.min(999, parseInt(raw, 10) || 1));
    setPicked(picked.map((p, j) => j === i ? { ...p, count: n } : p));
  };
  const setSpotter = (i, spotter) => setPicked(picked.map((p, j) =>
    j === i ? { ...p, firstSpotter: spotter } : p));
  const addCreature = (creatureId) => {
    if (picked.some(p => p.creatureId === creatureId)) return;
    // Default first-spotter to whoever's actively logging — saves a tap.
    // User can change with the R/S/— toggle on the sighting row.
    const defaultSpotter = state.activeUserId || null;
    setPicked([...picked, { creatureId, count: 1, firstSpotter: defaultSpotter }]);
  };
  // Swap the creatureId at index `i`, preserving the existing count and
  // firstSpotter. No-ops if the target is already in the list at another index.
  const swapCreatureAt = (i, newCreatureId) => {
    if (picked.some((p, j) => j !== i && p.creatureId === newCreatureId)) return;
    setPicked(picked.map((p, j) => j === i ? { ...p, creatureId: newCreatureId } : p));
  };

  // Picker state — when swapTarget !== null, picking a creature replaces
  // the sighting at that index instead of appending a new one.
  const [pickerOpen, setPickerOpen] = React.useState(false);
  const [swapTarget, setSwapTarget] = React.useState(null);
  const [search, setSearch] = React.useState('');
  const closePicker = () => { setPickerOpen(false); setSwapTarget(null); setSearch(''); };
  const openPickerForAdd = () => { setSwapTarget(null); setPickerOpen(true); };
  const openPickerForSwap = (i) => { setSwapTarget(i); setPickerOpen(true); };
  const handlePick = (creatureId) => {
    if (swapTarget !== null) swapCreatureAt(swapTarget, creatureId);
    else addCreature(creatureId);
    closePicker();
  };
  const pickedIds = new Set(
    swapTarget !== null
      ? picked.filter((_, j) => j !== swapTarget).map(p => p.creatureId) // hide everything except the one being swapped
      : picked.map(p => p.creatureId)
  );
  const candidates = state.creatures
    .filter(c => !region || (c.regions || []).indexOf(region) >= 0)
    .filter(c => !pickedIds.has(c.id))
    .filter(c => !search || c.commonName.toLowerCase().indexOf(search.toLowerCase()) >= 0);
  const rarityOrder = { mythic: 6, legendary: 5, epic: 4, rare: 3, uncommon: 2, common: 1 };
  const candidatesByFamily = candidates.reduce((acc, c) => {
    (acc[c.familyType] = acc[c.familyType] || []).push(c);
    return acc;
  }, {});
  Object.values(candidatesByFamily).forEach(arr =>
    arr.sort((a, b) => (rarityOrder[b.rarity] || 0) - (rarityOrder[a.rarity] || 0))
  );
  const familyOrder = [
    'shark','ray','turtle','reefFish','macro','pelagic',
    'cephalopod','mammal','invertebrate','crustacean',
    'coral','sponge','wreck','aircraft',
  ];
  // Catch unknown families at the bottom in registry order
  Object.keys(candidatesByFamily).forEach(f => {
    if (familyOrder.indexOf(f) < 0) familyOrder.push(f);
  });
  const sortedFamilies = familyOrder.filter(f => candidatesByFamily[f]);

  // Suggested = top-rarity not-yet-picked in this region, max 5
  const suggested = candidates.slice().sort((a, b) =>
    (rarityOrder[b.rarity] || 0) - (rarityOrder[a.rarity] || 0)
  ).slice(0, 5);

  const familyLabel = (f) => ({
    shark: 'Sharks', ray: 'Rays', turtle: 'Turtles', reefFish: 'Reef fish',
    macro: 'Macro', pelagic: 'Pelagic', cephalopod: 'Cephalopods',
    mammal: 'Mammals', invertebrate: 'Inverts', crustacean: 'Crustaceans',
    coral: 'Coral', sponge: 'Sponges', wreck: 'Wrecks', aircraft: 'Aircraft',
  }[f] || f);

  return (
    <div>
      <div className="dd-h-section" style={{ marginBottom: 8 }}>Sightings on this dive</div>
      {picked.length === 0 && (
        <div className="dd-glass" style={{ padding: 14, borderRadius: 14, fontSize: 12, color: 'var(--ink-3)', textAlign: 'center', lineHeight: 1.5 }}>
          No sightings yet. Tap a suggestion below or "Add sighting" to pick from the Dex.
        </div>
      )}
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
        {picked.map((p, i) => {
          const cr = lookup(p.creatureId);
          const isSwapping = swapTarget === i;
          return (
            <RarityFrame key={p.creatureId + i} rarity={cr.rarity} style={isSwapping ? { boxShadow: '0 0 0 2px rgba(49,215,255,0.55)' } : {}}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
                {/* Identity row: tap silhouette/name to swap creature, preserves count + spotter.
                    Uses div+role=button instead of <button> so the nested input/buttons stay valid. */}
                <div role="button" tabIndex={0} onClick={() => openPickerForSwap(i)}
                  onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openPickerForSwap(i); } }}
                  title="Tap to change species" style={{
                    cursor: 'pointer', background: 'transparent', padding: 0, color: 'inherit', textAlign: 'left',
                    display: 'flex', alignItems: 'center', gap: 12, flex: '1 1 auto', minWidth: 0,
                  }}>
                  <RarityRing rarity={cr.rarity} size={56}>
                    <CrSilhouette id={p.creatureId} size={42}/>
                  </RarityRing>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ display: 'flex', alignItems: 'flex-start', gap: 6 }}>
                      <span style={{ fontSize: 13.5, fontWeight: 700, color: '#EAF5FF', lineHeight: 1.15, overflowWrap: 'break-word', minWidth: 0, flex: '1 1 auto' }}>{cr.name}</span>
                      <span style={{ fontSize: 10, color: 'var(--ink-3)', flex: '0 0 auto', marginTop: 2 }}>✎</span>
                    </div>
                    <div style={{ fontSize: 10.5, color: 'var(--r-color)', letterSpacing: '0.1em', fontWeight: 700, textTransform: 'uppercase', marginTop: 2 }}>{cr.rarity}</div>
                    <div style={{ display: 'flex', gap: 6, marginTop: 8, alignItems: 'center' }}>
                      {/* Count stepper — type a number directly, or use −/+ */}
                      <div style={{ display: 'flex', alignItems: 'center', gap: 2, padding: '2px 4px', borderRadius: 8, background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(130,236,255,0.10)' }} onClick={e => e.stopPropagation()}>
                        <span role="button" onClick={(e) => { e.stopPropagation(); incCount(i, -1); }} className="dd-btn" style={{ width: 22, height: 22, borderRadius: 6, background: 'transparent', color: 'var(--ink-3)', fontSize: 14, fontWeight: 700, display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>−</span>
                        <span style={{ fontSize: 11.5, fontWeight: 700, color: 'var(--ink-3)' }}>×</span>
                        <input
                          type="number" inputMode="numeric" min="1" max="999"
                          value={p.count}
                          onChange={e => setCount(i, e.target.value)}
                          onFocus={e => e.target.select()}
                          onClick={e => e.stopPropagation()}
                          className="dd-num"
                          style={{
                            width: 36, height: 22, border: 0, outline: 'none',
                            background: 'transparent', color: '#EAF5FF',
                            fontSize: 12, fontWeight: 700, textAlign: 'center',
                            appearance: 'textfield', MozAppearance: 'textfield',
                          }}
                        />
                        <span role="button" onClick={(e) => { e.stopPropagation(); incCount(i, 1); }} className="dd-btn" style={{ width: 22, height: 22, borderRadius: 6, background: 'transparent', color: 'var(--ink-3)', fontSize: 14, fontWeight: 700, display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>+</span>
                      </div>
                      {/* Spotter toggle */}
                      <div style={{ display: 'flex', gap: 2, padding: 2, borderRadius: 8, background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(130,236,255,0.10)' }} onClick={e => e.stopPropagation()}>
                        {[{ id: null, label: '—' }, { id: 'rick', label: 'R' }, { id: 'siet', label: 'S' }].map(opt => {
                          const on = p.firstSpotter === opt.id;
                          return (
                            <span key={opt.label} role="button" onClick={(e) => { e.stopPropagation(); setSpotter(i, opt.id); }} className="dd-btn" style={{
                              width: 22, height: 22, borderRadius: 6, fontSize: 10, fontWeight: 700,
                              display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                              background: on ? 'rgba(49,215,255,0.18)' : 'transparent',
                              color: on ? '#82ECFF' : 'var(--ink-3)',
                              border: on ? '1px solid rgba(49,215,255,0.36)' : '1px solid transparent',
                            }}>{opt.label}</span>
                          );
                        })}
                      </div>
                    </div>
                  </div>
                </div>
                <button onClick={() => removeAt(i)} className="dd-btn" style={{ width: 30, height: 30, borderRadius: 10, background: 'rgba(255,255,255,0.04)', color: 'var(--ink-3)', border: '1px solid rgba(130,236,255,0.12)' }}>×</button>
              </div>
            </RarityFrame>
          );
        })}
      </div>

      <button onClick={() => (pickerOpen ? closePicker() : openPickerForAdd())} className="dd-btn dd-btn-ghost" style={{ marginTop: 12, width: '100%', height: 46, borderRadius: 14, fontSize: 13, borderStyle: 'dashed' }}>
        {pickerOpen
          ? (swapTarget !== null ? '× Cancel swap' : '× Close picker')
          : '＋ Add sighting from Dex'}
      </button>

      {pickerOpen && (
        <GlassCard style={{ padding: 12, borderRadius: 14, marginTop: 8, ...(swapTarget !== null ? { borderColor: 'rgba(49,215,255,0.36)' } : {}) }}>
          {swapTarget !== null && (
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8, padding: '6px 8px', borderRadius: 8, background: 'rgba(49,215,255,0.08)', border: '1px solid rgba(49,215,255,0.22)' }}>
              <span style={{ fontSize: 11, fontWeight: 700, color: '#82ECFF', letterSpacing: '0.08em' }}>
                Swap → {lookup(picked[swapTarget].creatureId).name}
              </span>
              <button onClick={closePicker} className="dd-btn" style={{ background: 'transparent', color: 'var(--ink-3)', fontSize: 11, fontWeight: 600 }}>Cancel</button>
            </div>
          )}
          <input
            value={search}
            onChange={e => setSearch(e.target.value)}
            placeholder={swapTarget !== null ? 'Search replacement…' : 'Search creature…'}
            style={{
              width: '100%', padding: '8px 10px', borderRadius: 10, fontSize: 13,
              background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(130,236,255,0.14)',
              color: '#EAF5FF', outline: 'none',
            }}
          />
          <div style={{ maxHeight: 320, overflowY: 'auto', marginTop: 10, scrollbarWidth: 'none' }}>
            {sortedFamilies.length === 0 && (
              <div style={{ fontSize: 11.5, color: 'var(--ink-3)', textAlign: 'center', padding: '10px 0' }}>
                No matches in this region.
              </div>
            )}
            {sortedFamilies.map(f => (
              <div key={f} style={{ marginBottom: 12 }}>
                <div className="dd-h-section" style={{ marginBottom: 6, fontSize: 9, opacity: 0.7 }}>{familyLabel(f)}</div>
                <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6 }}>
                  {candidatesByFamily[f].map(c => (
                    <button key={c.id} onClick={() => handlePick(c.id)} className={`dd-btn dd-rarity-${c.rarity}`} style={{
                      display: 'flex', alignItems: 'center', gap: 8, padding: '6px 8px', borderRadius: 10,
                      background: 'rgba(255,255,255,0.04)', border: '1px solid var(--r-color)',
                      color: '#EAF5FF', textAlign: 'left',
                    }}>
                      <CrSilhouette id={c.id} size={32}/>
                      <div style={{ flex: 1, minWidth: 0 }}>
                        <div style={{ fontSize: 11.5, fontWeight: 700, lineHeight: 1.1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{c.commonName}</div>
                        <div style={{ fontSize: 9, color: 'var(--r-color)', letterSpacing: '0.08em', fontWeight: 700, textTransform: 'uppercase' }}>{c.rarity}{c.discovered ? '' : ' · new'}</div>
                      </div>
                    </button>
                  ))}
                </div>
              </div>
            ))}
          </div>
        </GlassCard>
      )}

      {suggested.length > 0 && (
        <>
          <div className="dd-h-section" style={{ marginTop: 18, marginBottom: 8 }}>Suggested · this region</div>
          <div style={{ display: 'flex', gap: 8, overflowX: 'auto', margin: '0 -18px', padding: '0 18px', scrollbarWidth: 'none' }}>
            {suggested.map(c => (
              <button key={c.id} onClick={() => addCreature(c.id)} className="dd-glass dd-btn" style={{
                flex: '0 0 108px', padding: '8px 6px', borderRadius: 12, textAlign: 'center', cursor: 'pointer',
                color: 'var(--ink)', whiteSpace: 'normal',
              }}>
                <CrSilhouette id={c.id} size={56}/>
                <div style={{ fontSize: 10, fontWeight: 600, marginTop: 4, lineHeight: 1.1, color: '#EAF5FF', overflowWrap: 'break-word' }}>{c.commonName}</div>
                <div style={{ fontSize: 8.5, color: 'var(--ink-3)', marginTop: 2, letterSpacing: '0.08em', textTransform: 'uppercase', fontWeight: 700 }}>＋ {c.rarity}</div>
              </button>
            ))}
          </div>
        </>
      )}
    </div>
  );
}

function LogStepVibe({ form, setForm, sightings }) {
  const { state } = useDiveDex();
  const set = (k) => (v) => setForm({ ...form, [k]: v });
  const moods = [
    { v: 'zen', e: '😌', l: 'Zen' }, { v: 'awe', e: '🤩', l: 'Awe' },
    { v: 'stoked', e: '🤿', l: 'Stoked' }, { v: 'rinsed', e: '🥶', l: 'Rinsed' },
  ];

  // Live Air Zen preview — same threshold the scoring engine uses
  const safeReserve = state.settings.safeReserveBar || 50;
  const qualifies = Number(form.endBar) >= safeReserve;
  const xp = window.DD_STATE.SCORING_CONFIG.xp;
  const prl = window.DD_STATE.SCORING_CONFIG.pearls;

  return (
    <div>
      <Field label="Post-dive mood">
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 8 }}>
          {moods.map(m => (
            <button key={m.v} onClick={() => set('mood')(m.v)} className="dd-btn" style={{
              padding: '12px 4px', borderRadius: 14, fontSize: 11.5, fontWeight: 600,
              background: form.mood === m.v ? 'rgba(49,215,255,0.16)' : 'rgba(255,255,255,0.04)',
              color: form.mood === m.v ? '#82ECFF' : 'var(--ink-2)',
              border: form.mood === m.v ? '1px solid rgba(49,215,255,0.36)' : '1px solid rgba(130,236,255,0.10)',
            }}>
              <div style={{ fontSize: 22 }}>{m.e}</div>{m.l}
            </button>
          ))}
        </div>
      </Field>
      <Field label="Current">
        <SegSwitch value={form.current} onChange={set('current')} options={[{value:'none', label:'None'},{value:'mild', label:'Mild'},{value:'strong', label:'Strong'},{value:'ripping', label:'Ripping'}]}/>
      </Field>
      <Field label="Snack recovery">
        <SegSwitch value={form.snack || 'ham'} onChange={set('snack')} options={[{value:'ham', label:'Ham & Cheese'},{value:'711', label:'7-Eleven'},{value:'sushi', label:'Bento'}]}/>
      </Field>
      <Field label="Quote of the dive" hint="optional · appears on Story Card">
        <div className="dd-glass" style={{ padding: '10px 12px', borderRadius: 12 }}>
          <input
            value={form.quote || ''}
            onChange={e => set('quote')(e.target.value)}
            placeholder="“Was that a hammerhead or an underwater Boeing?”"
            style={{ background: 'transparent', border: 0, outline: 'none', color: '#fff', width: '100%', fontFamily: 'Manrope', fontStyle: 'italic', fontSize: 13.5 }}
          />
        </div>
      </Field>
      {/* Air Zen bonus — actual qualification based on current end-of-dive bar. */}
      <div className="dd-glass" style={{
        padding: 14, borderRadius: 16, marginTop: 6,
        background: qualifies
          ? 'linear-gradient(160deg, rgba(99,255,210,0.14), rgba(11,42,68,0.4))'
          : 'linear-gradient(160deg, rgba(255,122,107,0.08), rgba(11,42,68,0.4))',
        borderColor: qualifies ? 'rgba(99,255,210,0.32)' : 'rgba(255,122,107,0.22)',
      }}>
        <div className="dd-h-section" style={{ color: qualifies ? '#63FFD2' : '#FF7A6B' }}>
          AIR ZEN BONUS · {qualifies ? 'EARNED' : 'NOT YET'}
        </div>
        <div style={{ display: 'flex', gap: 8, marginTop: 10, flexWrap: 'wrap' }}>
          {qualifies ? <>
            <Chip variant="foam">Surfaced ≥ {safeReserve} bar · +{xp.airZenBonus} XP</Chip>
            <Chip variant="foam">+{prl.airZenBonus} Pearls</Chip>
          </> : (
            <Chip variant="coral">Surface at {safeReserve}+ bar to earn · currently {form.endBar || 0} bar</Chip>
          )}
        </div>
        <div style={{ fontSize: 10.5, color: 'var(--ink-3)', marginTop: 8, lineHeight: 1.4 }}>
          Air Zen rewards calm, safe diving — not pushing limits. Edit your end-of-dive pressure on the Basics step.
        </div>
      </div>
    </div>
  );
}

function LogStepRecapPreview({ form, sightings, editing, onGo }) {
  const { state } = useDiveDex();
  if (editing) {
    // Edits don't re-score — show a simple summary instead of the unlock preview.
    return (
      <div>
        <div className="dd-glass dd-reveal" style={{ padding: 18, borderRadius: 20, textAlign: 'center', background: 'linear-gradient(160deg, rgba(49,215,255,0.14), rgba(5,11,20,0.6))', borderColor: 'rgba(49,215,255,0.28)' }}>
          <div style={{ fontSize: 28 }}>✎</div>
          <div className="dd-h1" style={{ fontSize: 22, marginTop: 4 }}>Save edits</div>
          <div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 4, lineHeight: 1.5 }}>
            Updates the dive record only. XP, Pearls and badges stay as they were the first time you saved.
          </div>
        </div>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginTop: 12 }}>
          <GlassCard style={{ padding: 12, borderRadius: 14 }}><StatCell label="Sightings" value={(sightings || []).length} color="#63FFD2" mono/></GlassCard>
          <GlassCard style={{ padding: 12, borderRadius: 14 }}><StatCell label="Max depth" value={form.maxDepth || 0} unit="m" mono/></GlassCard>
        </div>
      </div>
    );
  }
  // Preview: compute the same scoring the action will apply.
  const preview = computeDiveScoring(
    form || {}, sightings || [], state.creatures,
    window.DD_STATE.SCORING_CONFIG, state.unlockedAchievementIds,
  );
  // Also surface "first-dive" if this would be the very first.
  const previewAchievements = preview.achievementsToUnlock.slice();
  if (state.dives.length === 0 && previewAchievements.indexOf('first-dive') < 0) {
    previewAchievements.unshift('first-dive');
  }
  const ach = (id) => state.achievements.find(a => a.id === id);
  return (
    <div>
      <div className="dd-glass dd-reveal" style={{ padding: 18, borderRadius: 20, textAlign: 'center', background: 'linear-gradient(160deg, rgba(215,180,106,0.16), rgba(5,11,20,0.6))', borderColor: 'rgba(215,180,106,0.3)' }}>
        <div style={{ fontSize: 28 }}>✨</div>
        <div className="dd-shimmer dd-h1" style={{ fontSize: 22, marginTop: 4 }}>Ready to log</div>
        <div style={{ fontSize: 12, color: 'var(--ink-3)', marginTop: 4 }}>Tap below to save and see recap</div>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginTop: 12 }}>
        <GlassCard style={{ padding: 12, borderRadius: 14 }}><StatCell label="Team XP" value={'+' + preview.totalXp.toLocaleString()} color="#D7B46A" mono/></GlassCard>
        <GlassCard style={{ padding: 12, borderRadius: 14 }}><StatCell label="Species" value={(sightings || []).length} color="#63FFD2" mono/></GlassCard>
      </div>
      <GlassCard style={{ padding: 12, borderRadius: 14, marginTop: 8 }}>
        <StatCell label="Pearls" value={'+' + preview.pearls} color="#EAF5FF" mono/>
      </GlassCard>
      {previewAchievements.length > 0 && (
        <GlassCard style={{ padding: 14, borderRadius: 16, marginTop: 12 }}>
          <div className="dd-h-section">Will unlock</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 8 }}>
            {previewAchievements.map(id => {
              const a = ach(id);
              if (!a) return null;
              return (
                <div key={id} style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                  <div style={{ width: 26, height: 26, borderRadius: 8, background: 'rgba(215,180,106,0.18)', border: '1px solid rgba(215,180,106,0.4)', color: '#FBE6A6', display:'grid', placeItems: 'center', fontWeight: 700 }}>{a.icon || '✦'}</div>
                  <span style={{ fontSize: 13, fontWeight: 600 }}>{a.name}</span>
                </div>
              );
            })}
          </div>
        </GlassCard>
      )}
    </div>
  );
}

Object.assign(window, {
  LockScreen,
  WelcomeScreen, LevelsScreen, LevelCard, DashboardScreen, LogDiveScreen,
  SightingsBuilder, Field, TextIn, SegSwitch,
});
