const { useState, useEffect, useLayoutEffect, useRef } = React;
const D = window.RetroData;
const emojify = text => text.replace(/<3/g, '❤️').replace(/:\)/g, '🙂');
const readStore = (key, fallback, storage = localStorage) => { try { const raw = storage.getItem(key); return raw ? JSON.parse(raw) : fallback; } catch { return fallback; } };
const writeStore = (key, value, storage = localStorage) => { try { storage.setItem(key, JSON.stringify(value)); return true; } catch { return false; } };
const pageRoutes = { dashboard: 'overview', retrospective: 'new-retrospective', actions: 'actions' };
const sessionFromHash = () => window.location.hash.match(/^#session\/([A-Za-z0-9_-]{1,128})$/)?.[1] || null;
const pageHash = (page, sessionId) => page === 'retrospective' && sessionId ? `#session/${sessionId}` : `#${pageRoutes[page]}`;
const pageFromHash = () => sessionFromHash() ? 'retrospective' : window.location.hash === '#retrospectives' ? 'dashboard' : Object.keys(pageRoutes).find(page => `#${pageRoutes[page]}` === window.location.hash);
const initialPage = () => {
  const linkedPage = pageFromHash();
  if (linkedPage) return linkedPage;
  try {
    const saved = localStorage.getItem('retrospective-activeTab');
    if (Object.prototype.hasOwnProperty.call(pageRoutes, saved)) return saved;
  } catch {}
  return 'dashboard';
};
const iconPaths = {
  grid: <><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/></>,
  layers: <><path d="m12 3 9 5-9 5-9-5 9-5Z"/><path d="m3 12 9 5 9-5M3 16l9 5 9-5"/></>,
  board: <><rect x="3" y="3" width="18" height="18" rx="3"/><path d="M9 3v18M15 3v18M6 7v4M12 7v7M18 7v2"/></>,
  plus: <path d="M12 5v14M5 12h14"/>,
  arrow: <path d="M5 12h14m-6-6 6 6-6 6"/>,
  'arrow-up': <path d="M6 18 18 6M6 6h12v12"/>,
  chevron: <path d="m9 5 7 7-7 7"/>,
  down: <path d="m6 9 6 6 6-6"/>,
  close: <path d="m6 6 12 12M6 18 18 6"/>,
  search: <><circle cx="10.5" cy="10.5" r="6.5"/><path d="m16 16 4.5 4.5"/></>,
  calendar: <><rect x="3" y="5" width="18" height="16" rx="3"/><path d="M7 3v4M17 3v4M3 11h18M7 15h2M13 15h2"/></>,
  users: <><circle cx="9" cy="8" r="3"/><path d="M3 21v-3a6 6 0 0 1 12 0v3M16 5a3 3 0 0 1 0 6M21 21v-3a6 6 0 0 0-3-5"/></>,
  check: <path d="m5 12 4 4L19 6"/>,
  'check-circle': <><circle cx="12" cy="12" r="9"/><path d="m8 12 3 3 5-6"/></>,
  circle: <circle cx="12" cy="12" r="8"/>,
  progress: <><circle cx="12" cy="12" r="8" strokeDasharray="3 4"/><path d="M12 4a8 8 0 0 1 0 16Z" fill="currentColor" stroke="none"/></>,
  clock: <><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></>,
  bulb: <><path d="M8 16c0-3-3-3-3-7a7 7 0 0 1 14 0c0 4-3 4-3 7M9 19h6M10 22h4M8 16h8"/></>,
  smile: <><circle cx="12" cy="12" r="9"/><path d="M8 14s1 3 4 3 4-3 4-3M8 9h.01M16 9h.01"/></>,
  sun: <><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2M5 5l1.5 1.5M17.5 17.5 19 19M5 19l1.5-1.5M17.5 6.5 19 5"/></>,
  moon: <path d="M20 14a9 9 0 0 1-10-11 9 9 0 1 0 10 11Z"/>,
  logout: <><path d="M9 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h4M10 12h11m-4-4 4 4-4 4"/></>,
  lock: <><rect x="5" y="10" width="14" height="11" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3M12 14v3"/></>,
  help: <><circle cx="12" cy="12" r="9"/><path d="M9 9a3 3 0 0 1 6 0c0 2-3 2-3 5M12 17h.01"/></>,
  menu: <path d="M4 6h16M4 12h16M4 18h16"/>,
  spark: <path d="m12 2 2.5 7.5L22 12l-7.5 2.5L12 22l-2.5-7.5L2 12l7.5-2.5L12 2Z"/>,
};
function Icon({ name, size = 20, className = '' }) { return <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.65" strokeLinecap="round" strokeLinejoin="round" className={className} aria-hidden="true">{iconPaths[name] || iconPaths.spark}</svg>; }
function Flower({ className = '', size = 36 }) { return <svg className={className} width={size} height={size} viewBox="0 0 80 80" fill="currentColor" aria-hidden="true"><g transform="translate(40 40)">{[0, 45, 90, 135].map(a => <ellipse key={a} rx="10" ry="36" transform={`rotate(${a})`}/>)}</g><circle cx="40" cy="40" r="9" className="flower-center"/></svg>; }
function ZendropMark({ size = 28 }) { return <svg className="zendrop-z" width={size} height={size} viewBox="775 99 50 56" fill="currentColor" aria-hidden="true"><g transform="matrix(1 0 0 -1 0 1684)"><path d="M812.6824 1581.964C817.0211 1581.915 820.3004 1579.622 821.5458 1575.582 822.5225 1572.413 821.895 1569.435 819.7528 1566.835 812.5588 1558.107 805.3665 1549.379 798.1685 1540.654 796.8483 1539.055 795.5286 1537.455 794.1676 1535.891 790.8288 1532.051 785.1846 1531.444 781.4671 1534.497 777.4396 1537.805 776.8223 1543.397 780.1345 1547.489 784.1654 1552.469 788.2831 1557.381 792.3469 1562.335 792.5917 1562.633 792.6927 1563.049 792.861 1563.41 792.4387 1563.541 792.0209 1563.767 791.5935 1563.787 790.1316 1563.855 788.6646 1563.914 787.2032 1563.863 782.2296 1563.693 778.0836 1567.597 778.0291 1572.78 777.9753 1577.916 781.8844 1581.852 787.1748 1581.957 791.4722 1582.042 795.7718 1581.974 800.0703 1581.974 804.2741 1581.974 808.4791 1582.011 812.6824 1581.964Z"/><path opacity=".5" d="M807.7623 1540.19C807.8646 1541.59 808.2919 1542.868 809.1725 1543.96 811.6636 1547.049 813.211 1548.973 815.8267 1551.836 816.7917 1552.893 817.5544 1552.383 817.8924 1551.563 819.4244 1547.85 820.2619 1545.594 821.6071 1541.772 823.1389 1537.42 819.7349 1532.701 815.1144 1532.599 814.6721 1532.59 814.2291 1532.615 813.7863 1532.624 810.0874 1533.2 807.4889 1536.459 807.7623 1540.19Z"/></g></svg>; }
function Brand() { return <div className="brand"><span className="brand-mark"><ZendropMark size={29}/></span><span>Team Retrospective</span></div>; }
function GoogleIcon() { return <svg width="19" height="19" viewBox="0 0 24 24" aria-hidden="true"><path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/><path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/><path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l3.66-2.84z"/><path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/></svg>; }
function TeamBadge({ team, dot = true }) { return <span className={`team-badge team-${D.teamIndex(team)}`}>{dot && <span className="team-dot"/>}{D.teamName(team)}</span>; }
function Avatar({ user }) { const [failed, setFailed] = useState(false); return user?.photoURL && !failed ? <img className="avatar" src={user.photoURL} alt="" onError={() => setFailed(true)}/> : <span className="avatar avatar-fallback">{(user?.displayName || 'You').split(' ').map(n => n[0]).slice(0, 2).join('')}</span>; }
function EmptyState({ icon = 'layers', title, text, action, onAction }) { return <div className="empty-state"><span className="empty-icon"><Icon name={icon} size={27}/></span><h3>{title}</h3><p>{text}</p>{action && <button className="button button-secondary" onClick={onAction}>{action}<Icon name="arrow" size={16}/></button>}</div>; }
function Modal({ title, onClose, children, className = '' }) {
  const ref = useRef(null);
  useEffect(() => {
    const previous = document.activeElement;
    const overflow = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    ref.current?.focus();
    const keydown = e => {
      if (e.key === 'Escape') onClose();
      if (e.key === 'Tab') {
        const items = ref.current?.querySelectorAll('button, a[href], input, select, textarea, [tabindex="0"]');
        if (!items?.length) { e.preventDefault(); return; }
        const first = items[0], last = items[items.length - 1];
        if (e.shiftKey && (document.activeElement === first || document.activeElement === ref.current)) { e.preventDefault(); last.focus(); }
        else if (!e.shiftKey && (document.activeElement === last || document.activeElement === ref.current)) { e.preventDefault(); first.focus(); }
      }
    };
    document.addEventListener('keydown', keydown);
    return () => { document.body.style.overflow = overflow; document.removeEventListener('keydown', keydown); previous?.focus(); };
  }, []);
  return <div className="modal-backdrop" onClick={e => e.target === e.currentTarget && onClose()}><section className={`modal ${className}`} ref={ref} tabIndex={-1} role="dialog" aria-modal="true" aria-label={title}><button className="icon-button modal-close" onClick={onClose} aria-label="Close dialog"><Icon name="close"/></button>{children}</section></div>;
}
function SignIn({ onSignIn, signingIn, authLoading, error }) {
  return <main className="login-page"><div className="login-content">
    <h1 className="login-brand"><ZendropMark size={34}/><span>Team Retrospective</span></h1>
    <button className="button google-button" disabled={signingIn || authLoading} onClick={onSignIn}>{signingIn || authLoading ? <span className="spinner"/> : <GoogleIcon/>}{signingIn ? 'Signing in…' : authLoading ? 'Connecting…' : 'Continue with Google'}</button>
    {error && <div className="inline-error" role="alert">{error}</div>}
  </div></main>;
}
function CompletionCelebration({ onComplete }) {
  const ref = useRef(null);
  const callback = useRef(onComplete);
  const finished = useRef(false);
  callback.current = onComplete;
  const finish = () => {
    if (finished.current) return;
    finished.current = true;
    callback.current();
  };
  useLayoutEffect(() => {
    if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    const animations = [];
    const animate = (element, frames, options) => {
      if (element?.animate) animations.push(element.animate(frames, { fill: 'both', ...options }));
    };
    ref.current.querySelectorAll('.zendrop-z path').forEach((path, index) => {
      const length = path.getTotalLength();
      path.style.stroke = '#fff';
      path.style.strokeWidth = '1.1';
      path.style.strokeDasharray = `${length}`;
      animate(path, [
        { strokeDashoffset: `${length}px`, fillOpacity: 0, strokeOpacity: 1 },
        { strokeDashoffset: '0px', fillOpacity: 0, strokeOpacity: 1, offset: .65 },
        { strokeDashoffset: '0px', fillOpacity: 1, strokeOpacity: 0 },
      ], { duration: 850, delay: index * 110, easing: 'ease-in-out' });
    });
    animate(ref.current.querySelector('.completion-aura'), [
      { opacity: 0, transform: 'scale(.55)' },
      { opacity: .85, transform: 'scale(1)', offset: .55 },
      { opacity: .55, transform: 'scale(1.1)' },
    ], { duration: 1300, easing: 'ease-out' });
    animate(ref.current.querySelector('.completion-ring'), [
      { opacity: 0, transform: 'scale(.6)' },
      { opacity: .45, offset: .2 },
      { opacity: 0, transform: 'scale(1.65)' },
    ], { duration: 1100, delay: 180, easing: 'ease-out' });
    animate(ref.current.querySelector('h2'), [
      { opacity: 0, transform: 'translateY(8px)' },
      { opacity: 1, transform: 'translateY(0)' },
    ], { duration: 400, delay: 430, easing: 'ease-out' });
    return () => animations.forEach(animation => animation.cancel());
  }, []);
  useEffect(() => {
    const timer = setTimeout(finish, 1600);
    return () => clearTimeout(timer);
  }, []);
  return <Modal title="Retrospective complete" onClose={finish} className="completion-modal">
    <div className="completion-content" ref={ref}>
      <div className="completion-symbol" aria-hidden="true"><span className="completion-aura"/><span className="completion-ring"/><ZendropMark size={92}/></div>
      <h2 role="status">Retrospective complete</h2>
    </div>
  </Modal>;
}
function App() {
  const [sdk, setSdk] = useState(null);
  const [user, setUser] = useState(null);
  const [authLoading, setAuthLoading] = useState(true);
  const [authError, setAuthError] = useState('');
  const [signingIn, setSigningIn] = useState(false);
  const [demo, setDemo] = useState(() => !sessionFromHash() && readStore('retro-demo-active', false, sessionStorage));
  const [page, setPage] = useState(initialPage);
  const [liveSessionId, setLiveSessionId] = useState(sessionFromHash);
  const [team, setTeam] = useState('all');
  const [search, setSearch] = useState('');
  const [retros, setRetros] = useState([]);
  const [actions, setActions] = useState([]);
  const [dataLoading, setDataLoading] = useState(true);
  const [dataError, setDataError] = useState('');
  const [revision, setRevision] = useState(0);
  const [toast, setToast] = useState(null);
  const [celebration, setCelebration] = useState(null);
  const [highlightedRetro, setHighlightedRetro] = useState(null);
  const [review, setReview] = useState(null);
  const [guide, setGuide] = useState(false);
  const [mobileNav, setMobileNav] = useState(false);
  const [theme, setTheme] = useState(document.documentElement.dataset.theme || 'dark');
  const [pendingActions, setPendingActions] = useState(new Set());
  const [completingActions, setCompletingActions] = useState(new Set());
  const pendingRef = useRef(new Set());
  const sessionVersion = useRef(0);
  const visibleUser = demo ? { displayName: 'Alex Morgan', email: 'demo@example.com' } : user;
  const restoringSession = authLoading && !demo;
  const workspaceLoading = restoringSession || dataLoading;
  const stats = D.statsFor(actions);
  const notify = (message, type = 'success') => setToast({ message, type, id: Date.now() });
  const navigate = (target, sessionId = null) => {
    if (!Object.prototype.hasOwnProperty.call(pageRoutes, target)) return;
    setPage(target); setSearch(''); setTeam('all'); setMobileNav(false);
    if (target === 'retrospective' && !sessionId && !demo && user) { try { sessionId = localStorage.getItem(`retro-active-session-${user.uid}`); } catch {} }
    setLiveSessionId(target === 'retrospective' ? sessionId : null);
    const hash = pageHash(target, sessionId);
    if (window.location.hash !== hash) window.history.pushState(null, '', hash);
    try { localStorage.setItem('retrospective-activeTab', target); } catch {}
    window.scrollTo({ top: 0, behavior: 'instant' });
  };
  useEffect(() => {
    if (!sessionFromHash() && window.location.hash !== pageHash(page)) window.history.replaceState(null, '', pageHash(page));
    const restorePage = () => {
      const restored = pageFromHash() || 'dashboard';
      const sessionId = sessionFromHash();
      if (window.location.hash !== pageHash(restored, sessionId)) window.history.replaceState(null, '', pageHash(restored, sessionId));
      setLiveSessionId(sessionId);
      setPage(restored);
      setCelebration(null);
      setSearch(''); setTeam('all'); setMobileNav(false); setReview(null); setGuide(false);
    };
    window.addEventListener('popstate', restorePage);
    window.addEventListener('hashchange', restorePage);
    return () => {
      window.removeEventListener('popstate', restorePage);
      window.removeEventListener('hashchange', restorePage);
    };
  }, []);
  useEffect(() => {
    try { localStorage.setItem('retrospective-activeTab', page); } catch {}
  }, [page]);

  useEffect(() => {
    let active = true, unsubscribe;
    const timeout = setTimeout(() => { if (active) { setAuthLoading(false); setAuthError('The connection is taking a little longer. Please refresh and try again.'); } }, 12000);
    window.firebaseReady.then(service => {
      if (!active) return;
      setSdk(service);
      unsubscribe = service.onAuthStateChanged(service.auth, account => {
        if (!active) return;
        clearTimeout(timeout);
        setUser(account?.email?.toLowerCase().endsWith('@zendrop.com') ? account : null);
        setAuthLoading(false);
        setAuthError('');
      }, () => { if (active) { setAuthLoading(false); setAuthError('We couldn’t connect to your account. Please refresh and try again.'); } });
    }).catch(() => { if (active) { clearTimeout(timeout); setAuthLoading(false); setAuthError('We couldn’t connect to your workspace. Check your connection and try again.'); } });
    return () => { active = false; clearTimeout(timeout); unsubscribe?.(); };
  }, []);

  useEffect(() => {
    let active = true;
    sessionVersion.current += 1;
    pendingRef.current.clear(); setPendingActions(new Set());
    setCompletingActions(new Set()); setCelebration(null); setHighlightedRetro(null);
    setDataError(''); setRetros([]); setActions([]);
    if (demo) {
      const stored = readStore('retro-demo-data', null, sessionStorage);
      const data = stored && Array.isArray(stored.retros) && Array.isArray(stored.actions) ? stored : D.makeDemo();
      setRetros(data.retros); setActions(data.actions); setDataLoading(false);
      return;
    }
    if (!user || !sdk) return;
    setDataLoading(true);
    Promise.all(['retrospectives', 'actionPoints'].map(name => sdk.getDocs(sdk.query(sdk.collection(sdk.db, name), sdk.orderBy('createdAt', 'desc'))).then(snapshot => snapshot.docs.map(doc => ({ ...doc.data(), id: doc.id }))))).then(([r, a]) => { if (active) { setRetros(r); setActions(a); } }).catch(() => { if (active) setDataError('Your workspace couldn’t be loaded. Please check your connection and try again.'); }).finally(() => { if (active) setDataLoading(false); });
    return () => { active = false; };
  }, [demo, user, sdk, revision]);
  useEffect(() => { if (demo && retros.length) writeStore('retro-demo-data', { retros, actions }, sessionStorage); }, [demo, retros, actions]);
  useEffect(() => { if (!toast) return; const timer = setTimeout(() => setToast(null), 5500); return () => clearTimeout(timer); }, [toast]);
  useEffect(() => { if (!highlightedRetro) return; const timer = setTimeout(() => setHighlightedRetro(null), 2800); return () => clearTimeout(timer); }, [highlightedRetro]);
  useEffect(() => { document.documentElement.dataset.theme = theme; try { localStorage.setItem('retro-theme-v2', theme); } catch {} }, [theme]);
  useEffect(() => { document.title = `${({ dashboard: 'Overview', retrospective: 'New retrospective', actions: 'Action board' })[page]} — Team Retrospective`; }, [page]);
  useEffect(() => { if (!mobileNav) return; const close = e => { if (e.key === 'Escape') setMobileNav(false); }; document.addEventListener('keydown', close); return () => document.removeEventListener('keydown', close); }, [mobileNav]);

  const signIn = async () => {
    if (!sdk) { setAuthError('Your workspace is still connecting. Please refresh and try again.'); return; }
    setSigningIn(true); setAuthError('');
    try {
      const result = await sdk.signInWithPopup(sdk.auth, sdk.provider);
      if (!result.user.email?.toLowerCase().endsWith('@zendrop.com')) { await sdk.signOut(sdk.auth); setAuthError('Please sign in with your @zendrop.com Google account.'); }
    } catch (error) { if (error.code !== 'auth/popup-closed-by-user') setAuthError(error.code === 'auth/popup-blocked' ? 'Allow pop-ups for this site, then try signing in again.' : 'Sign-in didn’t finish. Please try again.'); }
    finally { setSigningIn(false); }
  };
  const exit = async () => {
    if (demo) { writeStore('retro-demo-active', false, sessionStorage); setDemo(false); navigate('dashboard'); return; }
    try { await sdk.signOut(sdk.auth); navigate('dashboard'); } catch { notify('We couldn’t sign you out. Please try again.', 'error'); }
  };
  const changeStatus = async (id, status) => {
    const action = actions.find(item => item.id === id);
    if (!action || action.status === status || !D.statuses.some(s => s.value === status) || pendingRef.current.has(id)) return;
    const version = sessionVersion.current;
    pendingRef.current.add(id); setPendingActions(new Set(pendingRef.current));
    if (status !== 'done') setActions(current => current.map(item => item.id === id ? { ...item, status } : item));
    try {
      if (!demo) await sdk.updateDoc(sdk.doc(sdk.db, 'actionPoints', id), { status });
      if (version !== sessionVersion.current) return;
      if (status === 'done') {
        setCompletingActions(current => new Set([...current, id]));
        if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) await new Promise(resolve => setTimeout(resolve, 460));
        if (version !== sessionVersion.current) return;
        setActions(current => current.map(item => item.id === id ? { ...item, status } : item));
      }
      if (version === sessionVersion.current) notify(status === 'done' ? 'Action completed.' : `Action moved to ${D.statuses.find(s => s.value === status).label.toLowerCase()}.`);
    } catch {
      if (version === sessionVersion.current) { setActions(current => current.map(item => item.id === id ? { ...item, status: action.status } : item)); notify('That change couldn’t be saved. Please try again.', 'error'); }
    } finally { if (version === sessionVersion.current) { pendingRef.current.delete(id); setPendingActions(new Set(pendingRef.current)); setCompletingActions(current => { const next = new Set(current); next.delete(id); return next; }); } }
  };
  const sharedSaved = ({ id, retro, actions: savedActions }) => {
    setRetros(current => [{ ...retro, id }, ...current.filter(item => item.id !== id)]);
    setActions(current => [...savedActions, ...current.filter(item => !savedActions.some(saved => saved.id === item.id))]);
    try { localStorage.removeItem(`retro-active-session-${user.uid}`); } catch {}
    setToast(null); setCelebration({ id });
  };
  const startLiveRetro = async (form, pendingId) => {
    const id = await window.RetroLive.create(sdk, user, form, pendingId);
    try { localStorage.setItem(`retro-active-session-${user.uid}`, id); localStorage.removeItem('retrospective-formData'); localStorage.removeItem(`retro-pending-session-${user.uid}`); } catch {}
    return id;
  };
  const saveRetro = async form => {
    const version = sessionVersion.current;
    const id = demo ? `demo-retro-${Date.now()}` : sdk.doc(sdk.collection(sdk.db, 'retrospectives')).id;
    const { retro, actions: newActions } = D.prepareSubmission(form, visibleUser.email, id);
    const savedActions = newActions.map((action, i) => ({ ...action, id: demo ? `${id}-action-${i}` : sdk.doc(sdk.collection(sdk.db, 'actionPoints')).id }));
    if (!demo) {
      const batch = sdk.writeBatch(sdk.db);
      batch.set(sdk.doc(sdk.db, 'retrospectives', id), retro);
      savedActions.forEach(({ id: actionId, ...action }) => batch.set(sdk.doc(sdk.db, 'actionPoints', actionId), action));
      await batch.commit();
    }
    if (version !== sessionVersion.current) return;
    setRetros(current => [{ ...retro, id }, ...current]); setActions(current => [...savedActions, ...current]);
    setToast(null); setCelebration({ id });
  };
  const finishCelebration = () => {
    if (!celebration) return;
    setHighlightedRetro(celebration.id); setCelebration(null); navigate('dashboard');
  };

  if (!visibleUser && !restoringSession) return <SignIn onSignIn={signIn} signingIn={signingIn} authLoading={authLoading} error={authError}/>;
  return <div className="app-shell">
    <a className="skip-link" href="#main-content">Skip to content</a>
    <header className="workspace-header"><div className="header-inner"><button className="brand-home" onClick={() => navigate('dashboard')} aria-label="Go to overview"><Brand/></button><nav className={`main-nav ${mobileNav ? 'is-open' : ''}`} aria-label="Main navigation">{[{ id: 'dashboard', label: 'Overview' }, { id: 'actions', label: 'Action board' }].map(item => <button key={item.id} className={page === item.id ? 'active' : ''} onClick={() => navigate(item.id)} aria-current={page === item.id ? 'page' : undefined}>{item.label}</button>)}</nav><div className="header-actions"><button className="icon-button theme-toggle" onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')} aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} theme`} title={`Switch to ${theme === 'light' ? 'dark' : 'light'} theme`}><Icon name={theme === 'light' ? 'moon' : 'sun'} size={20}/></button>{visibleUser && <><span className="header-divider"/><Avatar user={visibleUser}/><button className="icon-button signout-button" onClick={exit} aria-label={demo ? 'Exit demo' : 'Sign out'} title={demo ? 'Exit demo' : 'Sign out'}><Icon name="logout" size={20}/></button></>}<button className="icon-button mobile-menu" onClick={() => setMobileNav(v => !v)} aria-label="Toggle navigation" aria-expanded={mobileNav}><Icon name={mobileNav ? 'close' : 'menu'} size={22}/></button></div></div></header>
    <div className="main-shell">
    {demo && <div className="demo-banner"><span>Demo workspace · Sample data</span><button onClick={exit}>Exit demo <Icon name="arrow" size={14}/></button></div>}
    <main id="main-content" className="main-content" tabIndex={-1}>
      {dataError && <div className="error-banner" role="alert"><span>{dataError}</span><button className="text-button" onClick={() => setRevision(v => v + 1)}>Try again <Icon name="arrow" size={16}/></button></div>}
      {page === 'dashboard' && <Overview user={visibleUser} retros={retros} actions={actions} loading={workspaceLoading} highlightedRetro={highlightedRetro} navigate={navigate} onReview={setReview} onTeam={name => { navigate('actions'); setTeam(name); }}/ >}
      {page === 'actions' && <ActionBoard actions={actions} loading={workspaceLoading} team={team} setTeam={setTeam} search={search} setSearch={setSearch} onChange={changeStatus} pending={pendingActions} completing={completingActions} onCreate={() => navigate('retrospective')}/>}
      {page === 'retrospective' && (liveSessionId && !demo
        ? sdk && user ? <LiveSession key={`${user.uid}-${liveSessionId}`} sdk={sdk} user={user} id={liveSessionId} onSaved={sharedSaved} onOverview={() => navigate('dashboard')}/>
          : <div className="live-loading" role="status"><span className="spinner"/> Connecting to the live retrospective…</div>
        : demo ? <Editor key="demo" demo ready={!celebration} onSave={saveRetro}/>
          : sdk && user ? <StartLiveSession sdk={sdk} user={user} onStart={startLiveRetro} onResume={id => navigate('retrospective', id)}/>
            : <div className="live-loading" role="status"><span className="spinner"/> Connecting to your retrospective…</div>)}

    </main></div>
    {toast && <div className={`toast ${toast.type}`} role={toast.type === 'error' ? 'alert' : 'status'}><span className="toast-icon"><Icon name={toast.type === 'error' ? 'help' : 'check'} size={17}/></span>{toast.message}<button className="icon-button" onClick={() => setToast(null)} aria-label="Dismiss notification"><Icon name="close" size={16}/></button></div>}
    {celebration && <CompletionCelebration key={celebration.id} onComplete={finishCelebration}/>}
    {review && <Review retro={review} onClose={() => setReview(null)}/>}
    {guide && <Modal title="A guide to good retros" onClose={() => setGuide(false)} className="guide-modal"><span className="eyebrow">MAKE SPACE FOR THE GOOD STUFF</span><h2>Good conversations.<br/>Better next steps.</h2><p className="muted">A simple rhythm for your next team retrospective.</p>{[{ title: 'Reflect, without rushing.', text: 'Give everyone a few quiet minutes to capture what went well and what could be better.' }, { title: 'Get curious, together.', text: 'Talk through the themes. Use +1 votes to see what resonates, and focus on learning rather than blame.' }, { title: 'Pick a small next step.', text: 'Turn each challenge into a concrete action. Agree who will take it forward, then track it on the action board.' }].map((step, i) => <div className="guide-step" key={step.title}><span>0{i + 1}</span><div><h3>{step.title}</h3><p>{step.text}</p></div></div>)}<button className="button button-primary" onClick={() => { setGuide(false); navigate('retrospective'); }}>Let’s reflect <Icon name="arrow" size={17}/></button></Modal>}
  </div>;
}
function PageHeading({ eyebrow, title, description, action }) { return <div className="page-heading"><div>{eyebrow && <span className="eyebrow">{eyebrow}</span>}<h1>{title}</h1><p>{description}</p></div>{action}</div>; }
function Overview({ user, retros, actions, loading, highlightedRetro, navigate, onReview }) {
  const stats = D.statsFor(actions);
  return <div className="page-enter overview">
    <div className="overview-heading"><div><span className="eyebrow">ZENDROP / TEAM WORKSPACE</span><h1><ZendropMark size={48}/>Team Retrospective</h1></div><button className="button button-primary" onClick={() => navigate('retrospective')}><Icon name="plus" size={21}/> New retrospective</button></div>
    <section className="progress-panel" aria-label="Action points overview"><div className="progress-panel-top"><h2>Team momentum</h2><button className="text-button" onClick={() => navigate('actions')}>View action board <Icon name="arrow-up" size={19}/></button></div><div className="progress-panel-body"><div className="completion-number"><strong>{loading ? '—' : stats.rate}<span>%</span></strong><p>of actions completed</p></div><div className="progress-detail"><div className="progress-metrics"><div><span className="metric-label"><i className="status-dot done"/>Done</span><strong>{loading ? '—' : stats.done}</strong></div><div><span className="metric-label"><i className="status-dot in-progress"/>In progress</span><strong>{loading ? '—' : stats.progress}</strong></div><div><span className="metric-label"><i className="status-dot todo"/>To do</span><strong>{loading ? '—' : stats.todo}</strong></div></div><div className="momentum-track" aria-hidden="true"><span className="done" style={{ width: `${stats.total ? stats.done / stats.total * 100 : 0}%` }}/><span className="in-progress" style={{ width: `${stats.total ? stats.progress / stats.total * 100 : 0}%` }}/><span className="todo" style={{ width: `${stats.total ? stats.todo / stats.total * 100 : 0}%` }}/></div><span className="total-actions">{loading ? 'Loading your actions…' : `${stats.total} actions across your team`}</span></div></div></section>
    <section className="recent-section"><div className="section-heading"><h2>Retrospectives</h2></div><RetroList retros={retros} loading={loading} highlightedRetro={highlightedRetro} onReview={onReview} onCreate={() => navigate('retrospective')}/></section>
  </div>;
}
function Filters({ team, onTeam, search, onSearch, placeholder }) { return <div className="filters"><label className="search-field"><Icon name="search" size={17}/><input type="search" aria-label={placeholder} placeholder={placeholder} value={search} onChange={e => onSearch(e.target.value)}/>{search && <button className="icon-button" onClick={() => onSearch('')} aria-label="Clear search"><Icon name="close" size={14}/></button>}</label></div>; }
function RetroList({ retros, loading, highlightedRetro, onReview, onCreate }) {
  const [limit, setLimit] = useState(6);
  if (loading) return <div className="list-loading" role="status" aria-label="Loading retrospectives">{[1, 2, 3].map(i => <div className="skeleton list-skeleton" key={i}/>)}</div>;
  if (!retros.length) return <EmptyState title="No retrospectives yet" text="Bring your team together for your first retrospective." action="Start a retrospective" onAction={onCreate}/>;
  return <><div className="retro-list">{retros.slice(0, limit).map(retro => {
    const wins = retro.whatWentWell?.length || 0;
    const issues = retro.negativePoints?.length || 0;
    return <button className={`retro-entry ${highlightedRetro === retro.id ? 'just-saved' : ''}`} onClick={() => onReview(retro)} key={retro.id}><span className={`retro-icon team-${D.teamIndex(retro.team)}`}><Icon name="layers" size={21}/></span><div className="retro-entry-body"><div className="retro-entry-title"><h3>{D.sessionTitle(retro)}</h3></div><div className="retro-entry-meta">{retro.sprintNumber && <><span>{D.formatDate(retro.date)}</span><span className="meta-dot">·</span></>}<span>{wins} win{wins !== 1 ? 's' : ''}</span><span className="meta-dot">·</span><span>{issues} improvement{issues !== 1 ? 's' : ''}</span></div></div><span className="retro-entry-arrow"><Icon name="arrow-up" size={17}/></span></button>;
  })}</div>{retros.length > limit && <div className="load-more"><button className="button button-secondary" onClick={() => setLimit(count => count + 6)}>Load more retrospectives <Icon name="down" size={16}/></button></div>}</>;
}
function ActionBoard({ actions, loading, team, setTeam, search, setSearch, onChange, pending, completing, onCreate }) {
  const filtered = actions.filter(action => (team === 'all' || D.teamName(action.team) === team) && [action.title, action.relatedIssue, action.sprintNumber, D.teamName(action.team)].join(' ').toLowerCase().includes(search.toLowerCase()));
  const stats = D.statsFor(filtered);
  return <div className="page-enter"><PageHeading title="Action board" description="Turn the conversation into progress." action={<button className="button button-primary" onClick={onCreate}><Icon name="plus" size={17}/> New retrospective</button>}/><div className="board-summary"><span className="board-summary-icon"><Icon name="spark" size={22}/></span><div><strong>{stats.done} of {stats.total} actions completed</strong><span>{stats.total ? 'Keep going. Progress adds up.' : 'Turn your next reflection into a step forward.'}</span></div><div className="board-summary-progress"><div style={{ width: `${stats.rate}%` }}/></div><strong className="board-summary-rate">{stats.rate}%</strong></div><Filters team={team} onTeam={setTeam} search={search} onSearch={setSearch} placeholder="Search action items…"/>{!loading && !filtered.length && <div className="board-empty-hint">{actions.length ? 'No actions match these filters. Try another team or search.' : 'Your action board is ready. Add an action to a retrospective to get started.'}</div>}<div className="kanban">{D.statuses.map(status => <KanbanColumn key={status.value} status={status} actions={filtered.filter(a => a.status === status.value)} loading={loading} onChange={onChange} pending={pending} completing={completing}/>)}</div><div className="board-tip"><Icon name="bulb" size={15}/> Move cards between columns, or use the status selector on each card.</div></div>;
}
function KanbanColumn({ status, actions, loading, onChange, pending, completing }) {
  const [over, setOver] = useState(false);
  return <section className={`kanban-column ${status.value} ${over ? 'drag-over' : ''}`} aria-label={status.label} onDragOver={e => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; setOver(true); }} onDragLeave={e => { if (!e.currentTarget.contains(e.relatedTarget)) setOver(false); }} onDrop={e => { e.preventDefault(); setOver(false); onChange(e.dataTransfer.getData('text/plain'), status.value); }}><header><span className="column-status-icon"><Icon name={status.icon} size={17}/></span><h2>{status.label}</h2><span className="column-count">{loading ? '—' : actions.length}</span><span className="column-line"/></header><div className="kanban-cards">{loading ? [1, 2].map(i => <div className="skeleton action-skeleton" key={i}/>) : actions.map(action => <article key={action.id} className={`action-card ${pending.has(action.id) ? 'is-saving' : ''} ${completing.has(action.id) ? 'is-completing' : ''}`} draggable={!pending.has(action.id)} onDragStart={e => { e.dataTransfer.setData('text/plain', action.id); e.dataTransfer.effectAllowed = 'move'; const card = e.currentTarget; requestAnimationFrame(() => { if (card.isConnected) card.classList.add('is-dragging'); }); }} onDragEnd={e => e.currentTarget.classList.remove('is-dragging')}><div className="action-card-top"><TeamBadge team={action.team}/><span className="action-sprint">{D.sessionTitle(action)}</span></div><h3>{action.title}</h3>{action.relatedIssue && <div className="related-issue"><Icon name="layers" size={14}/><p>{action.relatedIssue}</p></div>}<div className="action-card-footer"><label className={`status-select ${action.status}`}><Icon name={status.icon} size={13}/><select aria-label={`Status for ${action.title}`} value={action.status} disabled={pending.has(action.id)} onChange={e => onChange(action.id, e.target.value)}>{D.statuses.map(s => <option value={s.value} key={s.value}>{s.label}</option>)}</select><Icon name="down" size={11}/></label>{completing.has(action.id) ? <span className="action-complete-bounce" role="status" aria-label="Action completed"><Icon name="check" size={18}/></span> : pending.has(action.id) ? <span className="spinner small"/> : action.status !== 'done' ? <button className="icon-button action-next" onClick={() => onChange(action.id, action.status === 'todo' ? 'in-progress' : 'done')} aria-label={`${action.status === 'todo' ? 'Start' : 'Complete'} ${action.title}`} title={action.status === 'todo' ? 'Start action' : 'Complete action'}><Icon name={action.status === 'todo' ? 'arrow' : 'check'} size={16}/></button> : <span className="completed-mark"><Icon name="check" size={15}/></span>}</div></article>)}{!loading && !actions.length && <div className="empty-column"><Icon name={status.icon} size={25}/><p>{status.value === 'done' ? 'Good things are on their way.' : status.value === 'in-progress' ? 'A little momentum starts here.' : 'A little room for what’s next.'}</p><span>Drop an action here</span></div>}</div></section>;
}
function AutoTextarea(props) {
  const ref = useRef(null);
  const resize = () => {
    const field = ref.current;
    if (!field) return;
    field.style.height = 'auto';
    field.style.height = `${field.scrollHeight}px`;
  };
  useLayoutEffect(resize, [props.value]);
  useEffect(() => {
    const field = ref.current;
    if (typeof ResizeObserver === 'undefined') {
      window.addEventListener('resize', resize);
      return () => window.removeEventListener('resize', resize);
    }
    let width = field.getBoundingClientRect().width;
    const observer = new ResizeObserver(() => {
      const nextWidth = field.getBoundingClientRect().width;
      if (nextWidth !== width) { width = nextWidth; resize(); }
    });
    observer.observe(field);
    return () => observer.disconnect();
  }, []);
  return <textarea {...props} ref={ref} rows={1}/>;
}
const POO_SVG = `<svg class="poo-buddy" viewBox="0 0 100 100">
  <defs><linearGradient id="poo-fill" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#a8703f"/><stop offset="1" stop-color="#6e4122"/></linearGradient></defs>
  <g fill="url(#poo-fill)">
    <rect x="8" y="66" width="84" height="28" rx="14"/>
    <rect x="18" y="44" width="64" height="28" rx="14"/>
    <rect x="29" y="25" width="42" height="24" rx="12"/>
    <path d="M42 27c2-10 12-13 18-21 4 8 2 17-6 21z"/>
  </g>
  <g fill="#fff" opacity=".22"><rect x="15" y="70" width="22" height="6" rx="3"/><rect x="25" y="48" width="16" height="5" rx="2.5"/><rect x="35" y="29" width="10" height="4" rx="2"/></g>
  <g class="poo-cheeks" fill="#ff8fa3" opacity=".75"><ellipse cx="27" cy="66" rx="6" ry="3.5"/><ellipse cx="73" cy="66" rx="6" ry="3.5"/></g>
  <g class="poo-eyes"><circle cx="39" cy="56" r="8" fill="#fff"/><circle cx="61" cy="56" r="8" fill="#fff"/>
    <g class="poo-pupils" fill="#2a1608"><circle cx="40" cy="57" r="4.2"/><circle cx="62" cy="57" r="4.2"/><circle cx="41.5" cy="55.2" r="1.4" fill="#fff"/><circle cx="63.5" cy="55.2" r="1.4" fill="#fff"/></g></g>
  <path d="M31 44.5q5-4 11-2M58 42.5q6-2 11 2" fill="none" stroke="#3b2210" stroke-width="2.6" stroke-linecap="round"/>
  <path d="M43 76q7 6 14 0" fill="none" stroke="#3b2210" stroke-width="3" stroke-linecap="round"/>
</svg>`;
const POO_STINK = '<svg class="poo-stink" viewBox="0 0 80 50"><path d="M18 48c-7-7 7-12 0-20s7-13 0-22"/><path d="M40 44c-7-7 7-12 0-20s7-13 0-20"/><path d="M62 48c-7-7 7-12 0-20s7-13 0-22"/></svg>';
function pooPeek(button) {
  const bounds = button.getBoundingClientRect();
  const stage = document.createElement('div');
  stage.className = 'poo-stage';
  stage.setAttribute('aria-hidden', 'true');
  stage.style.left = `${bounds.left + bounds.width / 2}px`;
  stage.style.top = `${bounds.top + 2}px`;
  stage.innerHTML = `${POO_STINK}<div class="poo-window">${POO_SVG}</div>`;
  document.body.appendChild(stage);
  const stink = stage.querySelector('.poo-stink');
  const buddy = stage.querySelector('.poo-buddy');
  const eyes = stage.querySelector('.poo-eyes');
  const pupils = stage.querySelector('.poo-pupils');
  const TOTAL = 2100;
  const at = ms => ms / TOTAL;
  // Peek out of the vote button, stretch, look around sheepishly, then duck back down.
  const peek = buddy.animate([
    { transform: 'translateY(105%) scale(1)' },
    { transform: 'translateY(-14%) scale(.86, 1.16)', offset: at(260), easing: 'ease-in' },
    { transform: 'translateY(0) scale(1.14, .88)', offset: at(380), easing: 'ease-out' },
    { transform: 'translateY(0) scale(.97, 1.03)', offset: at(500) },
    { transform: 'translateY(0) scale(1)', offset: at(620) },
    { transform: 'translateY(0) rotate(-5deg)', offset: at(950) },
    { transform: 'translateY(0) rotate(5deg)', offset: at(1300) },
    { transform: 'translateY(0) rotate(0) scale(1)', offset: at(1600) },
    { transform: 'translateY(-6%) scale(.94, 1.08)', offset: at(1720), easing: 'ease-in' },
    { transform: 'translateY(105%) scale(1)' },
  ], { duration: TOTAL, easing: 'ease-out', fill: 'both' });
  pupils.animate([
    { transform: 'translate(0, 0)' },
    { transform: 'translate(-3px, 0)', offset: at(800) }, { transform: 'translate(-3px, 0)', offset: at(1050) },
    { transform: 'translate(3px, 0)', offset: at(1150) }, { transform: 'translate(3px, 0)', offset: at(1400) },
    { transform: 'translate(0, 1px)', offset: at(1500) }, { transform: 'translate(0, 1px)' },
  ], { duration: TOTAL, easing: 'ease-in-out', fill: 'both' });
  eyes.animate([
    { transform: 'scaleY(1)' }, { transform: 'scaleY(1)', offset: at(640) },
    { transform: 'scaleY(.1)', offset: at(700) }, { transform: 'scaleY(1)', offset: at(770) }, { transform: 'scaleY(1)' },
  ], { duration: TOTAL, fill: 'both' });
  stink.animate([
    { opacity: 0, strokeDashoffset: 60, transform: 'translate(-50%, 8px)' },
    { opacity: .85, strokeDashoffset: 0, transform: 'translate(-50%, 0)', offset: .55 },
    { opacity: 0, strokeDashoffset: -40, transform: 'translate(-50%, -14px)' },
  ], { duration: 1100, delay: 520, easing: 'ease-out', fill: 'both' });
  setTimeout(() => {
    button.closest('.reflection-note')?.animate([
      { transform: 'rotate(0)' }, { transform: 'rotate(-.6deg)' }, { transform: 'rotate(.5deg)' }, { transform: 'rotate(0)' },
    ], { duration: 320, easing: 'ease-out' });
  }, 260);
  peek.finished.finally(() => stage.remove());
}
const voteMilestoneFinishes = new WeakMap();
const VOTE_FILL_DURATION = 720;
const VOTE_OUTLINE_DURATION = 1720;
const VOTE_BOUNCE_DURATION = 440;
function traceVoteOutline(note, fill, fromWidth) {
  let finish;
  voteMilestoneFinishes.set(note, new Promise(resolve => { finish = resolve; }));
  const startFraction = Math.max(0, Math.min(1, fromWidth.endsWith('%')
    ? parseFloat(fromWidth) / 100
    : parseFloat(fromWidth) / Math.max(1, fill.parentElement.getBoundingClientRect().width)));
  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  const head = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  svg.setAttribute('class', 'vote-milestone-trace');
  svg.setAttribute('aria-hidden', 'true');
  path.setAttribute('pathLength', '100');
  head.setAttribute('pathLength', '100');
  head.setAttribute('class', 'vote-milestone-head');
  svg.appendChild(path);
  svg.appendChild(head);
  let initialProgress = 0;
  const resize = () => {
    const { width, height } = note.getBoundingClientRect();
    const right = width - .5, bottom = height - .5;
    const radius = Math.max(0, Math.min(parseFloat(getComputedStyle(note).borderBottomLeftRadius) - .5, right / 2, bottom / 2));
    svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
    // The same stroke carries the existing bottom fill and the entire milestone animation.
    const bottomLength = width - 2 * (radius + .5);
    const perimeter = 2 * bottomLength + 2 * (height - 2 * (radius + .5)) + 2 * Math.PI * radius;
    initialProgress = 100 * bottomLength * startFraction / perimeter;
    const outline = `M ${radius + .5} ${bottom} H ${right - radius} A ${radius} ${radius} 0 0 0 ${right} ${bottom - radius} V ${radius + .5} A ${radius} ${radius} 0 0 0 ${right - radius} .5 H ${radius + .5} A ${radius} ${radius} 0 0 0 .5 ${radius + .5} V ${bottom - radius} A ${radius} ${radius} 0 0 0 ${radius + .5} ${bottom}`;
    path.setAttribute('d', outline);
    head.setAttribute('d', outline);
  };
  resize();
  note.classList.add('is-tracing-votes');
  note.appendChild(svg);
  const observer = typeof ResizeObserver === 'function' ? new ResizeObserver(resize) : null;
  observer?.observe(note);
  const draw = path.animate([{ strokeDashoffset: `${100 - initialProgress}` }, { strokeDashoffset: '0' }], {
    duration: VOTE_OUTLINE_DURATION, easing: 'linear', fill: 'both',
  });
  const movingGlow = head.animate([{ strokeDashoffset: `${1 - initialProgress}` }, { strokeDashoffset: '-99' }], {
    duration: VOTE_OUTLINE_DURATION, easing: 'linear', fill: 'both',
  });
  const animations = [draw, movingGlow];
  let disposed = false;
  const cleanup = (completed = false) => {
    if (disposed) return;
    disposed = true;
    finish(completed);
    animations.forEach(animation => animation.cancel());
    observer?.disconnect();
    svg.remove();
    note.classList.remove('is-tracing-votes');
  };
  draw.finished.then(async () => {
    if (disposed) return;
    note.classList.remove('is-tracing-votes');
    const fadeOutline = svg.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 260, easing: 'ease-out', fill: 'forwards' });
    const bounce = note.animate([
      { transform: 'translateY(0) scale(1)' },
      { transform: 'translateY(-2px) scale(1.012)', offset: .35 },
      { transform: 'translateY(.5px) scale(.998)', offset: .7 },
      { transform: 'translateY(0) scale(1)' },
    ], { duration: VOTE_BOUNCE_DURATION, easing: 'ease-out' });
    animations.push(fadeOutline, bounce);
    await bounce.finished;
    cleanup(true);
  }).catch(() => {});
  return cleanup;
}
function VoteProgress({ votes }) {
  const count = Math.max(0, Math.floor(Number(votes) || 0));
  const filled = Math.min(5, count);
  const fillRef = useRef(null);
  const previousCount = useRef(count);
  const animations = useRef([]);
  const stopOutline = useRef(null);
  useLayoutEffect(() => {
    const previous = previousCount.current;
    previousCount.current = count;
    const fill = fillRef.current;
    // Continue from the visible fill if another like arrives mid-animation.
    const fromWidth = fill && animations.current.some(animation => animation.playState === 'running')
      ? getComputedStyle(fill).width : `${Math.min(previous, 5) * 20}%`;
    animations.current.forEach(animation => animation.cancel());
    animations.current = [];
    if (!fill?.animate || count <= previous || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    if (Math.min(previous, 5) < filled) {
      if (filled === 5) {
        stopOutline.current?.();
        stopOutline.current = traceVoteOutline(fill.closest('.reflection-note'), fill, fromWidth);
      } else {
        animations.current.push(fill.animate([{ width: fromWidth }, { width: `${filled * 20}%` }], {
          duration: VOTE_FILL_DURATION, easing: 'cubic-bezier(.22, 1, .36, 1)',
        }));
      }
    }
    if (filled === 5) return;
    animations.current.push(fill.animate([
      { filter: 'brightness(1)' }, { filter: 'brightness(1.8)', offset: .25 }, { filter: 'brightness(1)' },
    ], { duration: 950, easing: 'ease-out' }));
    animations.current.push(fill.firstElementChild.animate([
      { opacity: 0, transform: 'scale(.5)' },
      { opacity: .9, transform: 'scale(1.2)', offset: .25 },
      { opacity: 0, transform: 'scale(1)' },
    ], { duration: 950, easing: 'ease-out' }));
  }, [count, filled]);
  useEffect(() => () => {
    animations.current.forEach(animation => animation.cancel());
    stopOutline.current?.();
  }, []);
  if (count === 0) return null;
  return <span className={`vote-progress ${filled === 5 ? 'is-complete' : filled === 4 ? 'is-almost' : ''}`} role="progressbar" aria-label="Votes toward fireworks" aria-valuemin={0} aria-valuemax={5} aria-valuenow={filled} aria-valuetext={filled === 5 ? `${count} votes. Fireworks milestone reached.` : `${count} of 5 votes to fireworks.`}>
    <span ref={fillRef} className="vote-progress-fill" style={{ width: `${filled * 20}%` }}><span className="vote-progress-glow" aria-hidden="true"/></span>
  </span>;
}
function PopularNoteEffect() {
  const ref = useRef(null);
  useEffect(() => {
    const element = ref.current;
    if (!element || typeof element.animate !== 'function' || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    const animations = [];
    const animate = (selector, frames, options) => {
      const layer = element.querySelector(selector);
      if (layer) animations.push(layer.animate(frames, { iterations: Infinity, ...options }));
    };
    // A stationary glow along the edges; the row keeps its original color.
    animate('.popular-note-mesh', [{ opacity: .035 }, { opacity: .075 }, { opacity: .035 }], { duration: 3600, easing: 'ease-in-out' });
    animate('.popular-note-rim', [{ opacity: .5 }, { opacity: .95 }, { opacity: .5 }], { duration: 3200, easing: 'ease-in-out' });
    element.querySelectorAll('.popular-note-sparkle').forEach((sparkle, index) => {
      animations.push(sparkle.animate([
        { opacity: 0, transform: 'scale(.4)' },
        { opacity: 1, transform: 'scale(1.12)' },
        { opacity: 0, transform: 'scale(.4)' },
      ], { duration: 2200, delay: [0, 400, 900, 1300][index], iterations: Infinity, easing: 'ease-in-out' }));
    });
    return () => animations.forEach(animation => animation.cancel());
  }, []);
  return <span className="popular-note-effect" ref={ref} aria-hidden="true">
    <span className="popular-note-mesh"/>
    <span className="popular-note-rim"/>
    {[{ top: '4px', left: '8%' }, { top: '4px', right: '22%' }, { bottom: '4px', left: '38%' }, { bottom: '4px', right: '8%' }].map((position, index) => <span className="popular-note-sparkle" key={index} style={position}>✦</span>)}
  </span>;
}
const startingSessions = new Map();
function StartLiveSession({ sdk, user, onStart, onResume }) {
  const [error, setError] = useState('');
  const [attempt, setAttempt] = useState(0);
  useEffect(() => {
    let active = true;
    setError('');
    const start = async () => {
      const activeKey = `retro-active-session-${user.uid}`;
      const pendingKey = `retro-pending-session-${user.uid}`;
      let existing, pendingId;
      try { existing = localStorage.getItem(activeKey); pendingId = localStorage.getItem(pendingKey); } catch {}
      if (existing) { onResume(existing); return; }
      if (!pendingId) {
        pendingId = sdk.doc(sdk.collection(sdk.db, 'liveSessions')).id;
        try { localStorage.setItem(pendingKey, pendingId); } catch {}
      }
      let promise = startingSessions.get(user.uid);
      if (!promise) {
        const form = D.normalizeForm(readStore('retrospective-formData', null));
        promise = onStart(form, pendingId);
        startingSessions.set(user.uid, promise);
        promise.finally(() => startingSessions.delete(user.uid)).catch(() => {});
      }
      const id = await promise;
      if (active) onResume(id);
    };
    start().catch(() => { if (active) setError('We couldn’t connect your live retrospective. Your existing draft is safe.'); });
    return () => { active = false; };
  }, [sdk, user.uid, attempt]);
  return <div className="live-loading" role="status">{error ? <><p>{error}</p><button className="button button-secondary" onClick={() => setAttempt(value => value + 1)}>Try again</button></> : <><span className="spinner"/> Starting your retrospective…</>}</div>;
}
function LiveSession({ sdk, user, id, onSaved, onOverview }) {
  const [state, setState] = useState(null);
  const [revision, setRevision] = useState(0);
  const controller = useRef(null);
  useEffect(() => {
    const connection = window.RetroLive.connect(sdk, user, id, setState);
    controller.current = connection;
    return () => connection.dispose();
  }, [sdk, user.uid, id, revision]);
  useEffect(() => {
    if (!state?.owner) return;
    try {
      const key = `retro-active-session-${user.uid}`;
      if (state.session.status === 'active') localStorage.setItem(key, id);
      else if (localStorage.getItem(key) === id) localStorage.removeItem(key);
    } catch {}
  }, [state?.owner, state?.session?.status, user.uid, id]);
  const retry = () => {
    if (!state?.form || state.error?.code === 'permission-denied') { setState(null); setRevision(value => value + 1); }
    else controller.current.retry().catch(() => {});
  };
  const completed = state?.session?.status === 'completed';
  const message = state?.error?.code === 'permission-denied'
    ? 'This live session is unavailable for your account. Ask the host to check the invite.'
    : state?.error?.message;
  if (!state?.form) return <div className="live-loading" role="status">{message ? <><p>{message}</p><button className="button button-secondary" onClick={retry}>Try again</button><button className="text-button" onClick={onOverview}>Back to overview</button></> : <><span className="spinner"/>{state?.offline ? 'Reconnect to open this live retrospective.' : 'Opening the live retrospective…'}</>}</div>;
  return <>
    {state.offline && <p className="live-sync-note" role="status">{state.owner ? "You’re offline. Your edits will sync when you reconnect." : "You’re offline. Reconnect to see updates and vote."}</p>}
    {completed && <p className="live-sync-note" role="status">This retrospective is finished.</p>}
    {state.error && <div className="error-banner live-sync-error" role="alert"><span>{message}</span>{state.error.code === 'conflict' ? <div><button className="text-button" onClick={() => controller.current.resolveConflict(false)}>Use shared version</button><button className="text-button" onClick={() => controller.current.resolveConflict(true)}>Keep my edits</button></div> : <button className="text-button" onClick={retry}>Retry sync</button>}</div>}
    <Editor ready={true} onSave={async () => onSaved(await controller.current.finish())} live={{
      form: state.form, owner: state.owner, completed, offline: state.offline,
      pointIds: state.session.pointIds, voteEvents: state.voteEvents,
      onChange: form => controller.current.edit(form), onVote: pointId => controller.current.vote(pointId),
      toolbar: <><time className="live-date" dateTime={new Date().toLocaleDateString('en-CA')}><Icon name="calendar" size={17}/>{D.formatDate(new Date(), { day: 'numeric', month: 'long', year: 'numeric' })}</time></>,
    }}/>
  </>;
}
function Editor({ demo, ready = true, onSave, live }) {
  const storageKey = demo ? 'retro-demo-draft' : 'retrospective-formData';
  const storage = demo ? sessionStorage : localStorage;
  const [localForm, setLocalForm] = useState(() => D.normalizeForm(readStore(storageKey, null, storage), demo ? '' : (() => { try { return localStorage.getItem('retrospective-selectedTeam') || ''; } catch { return ''; } })()));
  const form = live ? live.form : localForm;
  const setForm = update => live ? live.onChange(typeof update === 'function' ? update(form) : update) : setLocalForm(update);
  const readOnly = !!live && (!live.owner || live.completed);
  const [pendingVotes, setPendingVotes] = useState(new Set());
  const voting = useRef(new Set());
  const [submitting, setSubmitting] = useState(false);
  const [slowSave, setSlowSave] = useState(false);
  const [error, setError] = useState('');
  const [deleteTarget, setDeleteTarget] = useState(null);
  const [draftSaved, setDraftSaved] = useState(true);
  const [pointSearch, setPointSearch] = useState('');
  const [editingPoint, setEditingPoint] = useState(null);
  const searchRef = useRef(null);
  const errorRef = useRef(null);
  const submitLock = useRef(false);
  const savedRef = useRef(false);
  const confettiTimers = useRef(new Set());
  const lastCelebration = useRef(0);
  useEffect(() => () => {
    confettiTimers.current.forEach(timer => clearTimeout(timer));
    confettiTimers.current.clear();
  }, []);
  useEffect(() => { if (!live && !savedRef.current) setDraftSaved(writeStore(storageKey, form, storage)); }, [form]);
  useEffect(() => { if (error) errorRef.current?.focus(); }, [error]);
  useEffect(() => {
    if (!submitting) { setSlowSave(false); return; }
    const timer = setTimeout(() => setSlowSave(true), 8000);
    return () => clearTimeout(timer);
  }, [submitting]);
  const change = (key, value) => setForm(current => ({ ...current, [key]: value }));
  const updateWin = (index, text) => setForm(current => ({ ...current, whatWentWell: current.whatWentWell.map((item, i) => i === index ? { ...item, text: emojify(text) } : item) }));
  const updateIssue = (index, issue) => setForm(current => ({ ...current, negativePoints: current.negativePoints.map((item, i) => i === index ? { ...item, issue: emojify(issue) } : item) }));
  const updateAction = (index, actionIndex, value) => setForm(current => ({ ...current, negativePoints: current.negativePoints.map((item, i) => i === index ? { ...item, actionItems: item.actionItems.map((a, j) => j === actionIndex ? emojify(value) : a) } : item) }));
  const focusField = (id, animate = false) => requestAnimationFrame(() => {
    const field = document.getElementById(id);
    (field || searchRef.current)?.focus();
    if (!animate || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    const row = field?.closest('.action-input-row, .reflection-note');
    if (!row || typeof row.animate !== 'function') return;
    row.getAnimations().forEach(animation => animation.cancel());
    const styles = getComputedStyle(row);
    const background = styles.backgroundColor;
    const highlight = styles.getPropertyValue('--note-border').trim() || background;
    row.animate([
      { opacity: 0, transform: 'translateY(10px) scale(.985)' },
      { opacity: 1, transform: 'translateY(0) scale(1)' },
    ], { duration: 340, easing: 'cubic-bezier(.16, 1, .3, 1)' });
    row.animate([
      { backgroundColor: background },
      { backgroundColor: highlight, offset: .25 },
      { backgroundColor: background },
    ], { duration: 800, easing: 'ease-out' });
  });
  const clearPointSearch = () => { setPointSearch(''); setEditingPoint(null); };
  const addWin = (after = form.whatWentWell.length - 1) => { clearPointSearch(); setForm(current => { const items = [...current.whatWentWell]; items.splice(after + 1, 0, { id: D.pointId(), text: '', votes: 0 }); return { ...current, whatWentWell: items }; }); focusField(`win-${after + 1}`, true); };
  const addIssue = (after = form.negativePoints.length - 1) => { clearPointSearch(); setForm(current => { const items = [...current.negativePoints]; items.splice(after + 1, 0, { id: D.pointId(), issue: '', votes: 0, actionItems: [''] }); return { ...current, negativePoints: items }; }); focusField(`issue-${after + 1}`, true); };
  const addAction = (index, after = form.negativePoints[index].actionItems.length - 1) => { setForm(current => ({ ...current, negativePoints: current.negativePoints.map((item, i) => { if (i !== index) return item; const items = [...item.actionItems]; items.splice(after + 1, 0, ''); return { ...item, actionItems: items }; }) })); focusField(`action-${index}-${after + 1}`, true); };
  const confirmDelete = () => {
    if (!deleteTarget) return;
    const { type, index, actionIndex } = deleteTarget;
    setEditingPoint(null);
    if (type === 'win') {
      setForm(current => ({ ...current, whatWentWell: current.whatWentWell.filter((_, i) => i !== index) }));
      focusField(`win-${Math.max(0, index - 1)}`);
    } else if (type === 'issue') {
      setForm(current => ({ ...current, negativePoints: current.negativePoints.filter((_, i) => i !== index) }));
      focusField(`issue-${Math.max(0, index - 1)}`);
    } else {
      setForm(current => ({ ...current, negativePoints: current.negativePoints.map((point, i) => i === index ? { ...point, actionItems: point.actionItems.filter((_, j) => j !== actionIndex) } : point) }));
      focusField(`action-${index}-${Math.max(0, actionIndex - 1)}`);
    }
    setDeleteTarget(null);
  };
  const celebrateVote = (pointId, reachedFive, clickedButton) => {
    const isWin = form.whatWentWell.some(point => point.id === pointId);
    if (!isWin && !form.negativePoints.some(point => point.id === pointId)) return;
    const button = clickedButton || document.querySelector(`[data-vote-point="${CSS.escape(pointId)}"]`);
    if (!isWin) {
      if (!button?.animate || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
      const bounds = button.getBoundingClientRect();
      if (bounds.bottom > 0 && bounds.top < window.innerHeight) pooPeek(button);
      return;
    }
    const bounds = button?.getBoundingClientRect();
    const visible = bounds && bounds.bottom > 0 && bounds.top < window.innerHeight && bounds.right > 0 && bounds.left < window.innerWidth;
    const origin = visible
      ? { x: Math.max(.04, Math.min(.96, (bounds.left + bounds.width / 2) / window.innerWidth)), y: Math.max(.08, Math.min(.92, (bounds.top + bounds.height / 2) / window.innerHeight)) }
      : { x: .85, y: .85 };
    if (button?.animate && !window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
      button.animate([{ transform: 'scale(1)' }, { transform: reachedFive ? 'scale(1.16)' : 'scale(1.08)', offset: .35 }, { transform: 'scale(1)' }], { duration: reachedFive ? 550 : 320, easing: 'ease-out' });
    }
    if (typeof window.confetti !== 'function' || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    const later = (callback, delay) => {
      const timer = setTimeout(() => {
        confettiTimers.current.delete(timer);
        callback();
      }, delay);
      confettiTimers.current.add(timer);
    };
    const launchFireworks = () => {
      window.confetti({
        particleCount: 350, spread: 110, startVelocity: 48, scalar: 1, ticks: 240,
        origin,
        colors: ['#a78bfa', '#8b5cf6', '#c4b5fd', '#e9d5ff', '#ffffff'],
        disableForReducedMotion: true, zIndex: 9999,
      });
      if (!reachedFive) return;
      [
        { delay: 700, x: .88, y: .4, angle: 135, startVelocity: 62 },
        { delay: 1600, x: .12, y: .35, angle: 45, startVelocity: 70 },
        { delay: 2400, x: .3, y: .82, angle: 80, startVelocity: 52 },
        { delay: 3500, x: .75, y: .78, angle: 105, startVelocity: 64 },
      ].forEach(({ delay, x, y, angle, startVelocity }) => {
        later(() => {
          window.confetti({
            particleCount: 350, spread: 360, startVelocity, decay: .9, gravity: .28,
            scalar: 1, ticks: 180, shapes: ['circle'],
            angle, origin: { x, y },
            colors: ['#a78bfa', '#8b5cf6', '#c4b5fd', '#e9d5ff', '#ffffff'],
            disableForReducedMotion: true, zIndex: 9999,
          });
        }, delay);
      });
    };
    if (reachedFive) {
      // Let React commit the fifth vote, then wait for this screen's border and bounce.
      later(() => {
        const finished = voteMilestoneFinishes.get(button?.closest('.reflection-note'));
        if (finished) finished.then(completed => { if (completed) launchFireworks(); });
        else later(launchFireworks, VOTE_OUTLINE_DURATION + VOTE_BOUNCE_DURATION);
      }, 0);
    } else {
      launchFireworks();
    }
  };
  useEffect(() => {
    for (const event of live?.voteEvents || []) {
      if (event.sequence <= lastCelebration.current) continue;
      lastCelebration.current = event.sequence;
      celebrateVote(event.pointId, event.reachedFive);
    }
  }, [live?.voteEvents]);
  const vote = async (key, index, button) => {
    const point = form[key][index];
    if (voting.current.has(point.id)) return;
    const reachedFive = point.votes === 4;
    if (live) {
      voting.current.add(point.id); setPendingVotes(new Set(voting.current)); setError('');
      try { await live.onVote(point.id); }
      catch (cause) { setError(cause.message || 'Your vote couldn’t be saved. Please try again.'); return; }
      finally { voting.current.delete(point.id); setPendingVotes(new Set(voting.current)); }
      return; // Confirmed snapshot events celebrate once on every connected screen.
    } else setForm(current => ({ ...current, [key]: current[key].map((item, i) => i === index ? { ...item, votes: item.votes + 1 } : item) }));
    celebrateVote(point.id, reachedFive, button);
  };
  const submit = async e => {
    e.preventDefault();
    if (readOnly || submitLock.current || savedRef.current) return;
    if (!ready) { setError('Your account is still reconnecting. Please try again in a moment.'); return; }
    submitLock.current = true; setError(''); setSubmitting(true);
    try { await onSave(form); savedRef.current = true; try { if (!live) storage.removeItem(storageKey); } catch {} }
    catch (error) { setError(live ? error.message || 'Your retrospective couldn’t be saved. Please try again.' : error.code ? 'We couldn’t save your retrospective. Your draft is still here. Please try again.' : error.message || 'Something went wrong. Please try again.'); }
    finally { if (!savedRef.current) { submitLock.current = false; setSubmitting(false); } }
  };
  const voteDisabled = point => !!live && (live.completed || live.offline || !live.pointIds.includes(point.id) || pendingVotes.has(point.id));
  const searchQuery = pointSearch.trim().toLowerCase();
  // Keep original indices for editing, voting, and deletion; saving always uses the full form.
  // Keep the focused point visible while its text is being edited out of a match.
  const visibleWins = form.whatWentWell.map((item, index) => ({ item, index })).filter(({ item }) => !readOnly || item.text.trim()).filter(({ item, index }) => !searchQuery || item.text.toLowerCase().includes(searchQuery) || editingPoint === `win-${index}`);
  const visibleIssues = form.negativePoints.map((point, index) => ({ point, index })).filter(({ point }) => !readOnly || point.issue.trim()).filter(({ point, index }) => !searchQuery || [point.issue, ...point.actionItems].some(text => text.toLowerCase().includes(searchQuery)) || editingPoint === `issue-${index}`);
  const winCount = visibleWins.filter(({ item }) => item.text.trim()).length;
  const issueCount = visibleIssues.filter(({ point }) => point.issue.trim()).length;
  return <><form className="page-enter editor" aria-label="New retrospective" onSubmit={submit}>
    {!draftSaved && <p className="draft-status unsaved" role="status">Draft storage is unavailable on this device. Keep this page open until you finish.</p>}
    <fieldset disabled={submitting || !ready} className="editor-fieldset"><legend className="sr-only">Your sprint reflections</legend>
      <div className="editor-toolbar"><div className="search-field editor-search">
        <Icon name="search" size={19}/>
        <input ref={searchRef} type="search" aria-label="Search retrospective points and actions" placeholder="Search points and actions…" value={pointSearch} onFocus={() => setEditingPoint(null)} onChange={e => { setPointSearch(e.target.value); setEditingPoint(null); }} onKeyDown={e => { if (e.key === 'Enter') e.preventDefault(); if (e.key === 'Escape') { e.preventDefault(); clearPointSearch(); } }}/>
        {pointSearch && <button type="button" className="icon-button" aria-label="Clear search" onClick={() => { clearPointSearch(); searchRef.current?.focus(); }}><Icon name="close" size={16}/></button>}
      </div>
      <div className="editor-meta">{live?.toolbar}</div></div>
      <p className="sr-only" role="status">{searchQuery ? `${winCount} matching wins and ${issueCount} matching improvements` : ''}</p>
      <div className="reflection-board"><section className="reflection-column wins-column"><header><span className="reflection-icon reflection-emoji" aria-hidden="true">🙂</span><div><h2>What went well <span>{winCount}</span></h2></div><span className="column-number">01</span></header><div className="reflection-notes">{visibleWins.map(({ item, index }) => <div key={item.id} onFocus={() => setEditingPoint(`win-${index}`)} className={`reflection-note win-note ${item.votes >= 5 ? 'is-popular' : ''}`}>{item.votes >= 5 && <PopularNoteEffect/>}<VoteProgress votes={item.votes}/><div className="note-topline">{!readOnly && form.whatWentWell.length > 1 && <button type="button" className="icon-button note-delete" onClick={() => setDeleteTarget({ type: 'win', index })} aria-label={`Remove win ${index + 1}`}><Icon name="close" size={15}/></button>}</div><AutoTextarea readOnly={readOnly} id={`win-${index}`} aria-label={`What went well ${index + 1}`} placeholder="What went well?" value={item.text} onChange={e => updateWin(index, e.target.value)} rows={1} onKeyDown={e => { if (!readOnly && e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); addWin(index); } }}/><div className="note-bottomline"><button type="button" className="vote-button" data-vote-point={item.id} disabled={voteDisabled(item)} onClick={e => vote('whatWentWell', index, e.currentTarget)} aria-label={`Add a vote to win ${index + 1}`}><Icon name="plus" size={13}/><span>{item.votes}</span><Icon name="spark" size={13}/></button></div></div>)}{searchQuery && !visibleWins.length && <p className="reflection-no-matches">No matching wins.</p>}{!readOnly && <button type="button" className="add-reflection" onClick={() => addWin()}><Icon name="plus" size={17}/> Add a win</button>}</div></section>
    <section className="reflection-column ideas-column"><header><span className="reflection-icon reflection-emoji" aria-hidden="true">💡</span><div><h2>What could improve <span>{issueCount}</span></h2></div><span className="column-number">02</span></header><div className="reflection-notes">{visibleIssues.map(({ point, index }) => <div onFocus={() => setEditingPoint(`issue-${index}`)} className="reflection-note idea-note" key={point.id}><div className="note-topline">{!readOnly && form.negativePoints.length > 1 && <button type="button" className="icon-button note-delete" onClick={() => setDeleteTarget({ type: 'issue', index })} aria-label={`Remove opportunity ${index + 1}`}><Icon name="close" size={15}/></button>}</div><AutoTextarea readOnly={readOnly} id={`issue-${index}`} aria-label={`What could improve ${index + 1}`} placeholder="What could improve?" value={point.issue} onChange={e => updateIssue(index, e.target.value)} rows={1} onKeyDown={e => { if (!readOnly && e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); addIssue(index); } }}/><div className="note-bottomline"><button type="button" className="vote-button" data-vote-point={point.id} disabled={voteDisabled(point)} onClick={e => vote('negativePoints', index, e.currentTarget)} aria-label={`Add a vote to opportunity ${index + 1}`}><Icon name="plus" size={13}/><span>{point.votes}</span><Icon name="spark" size={13}/></button></div>{point.issue.trim() && (!readOnly || point.actionItems.some(action => action.trim())) && <div className="action-inputs"><span className="action-inputs-label"><Icon name="arrow" size={13}/> NEXT STEPS</span>{point.actionItems.map((action, ai) => ({ action, ai })).filter(({ action }) => !readOnly || action.trim()).map(({ action, ai }) => <div className="action-input-row" key={ai}><Icon name="circle" size={14}/><AutoTextarea readOnly={readOnly} id={`action-${index}-${ai}`} aria-label={`Action ${ai + 1} for opportunity ${index + 1}`} value={action} placeholder="One small, concrete next step…" rows={1} onChange={e => updateAction(index, ai, e.target.value)} onKeyDown={e => { if (!readOnly && e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); addAction(index, ai); } }}/>{!readOnly && point.actionItems.length > 1 && <button type="button" className="icon-button" onClick={() => setDeleteTarget({ type: 'action', index, actionIndex: ai })} aria-label={`Remove action ${ai + 1} for opportunity ${index + 1}`}><Icon name="close" size={13}/></button>}</div>)}{!readOnly && <button type="button" className="add-action" onClick={() => addAction(index)}><Icon name="plus" size={12}/> Add an action</button>}</div>}</div>)}{searchQuery && !visibleIssues.length && <p className="reflection-no-matches">No matching improvements.</p>}{!readOnly && <button type="button" className="add-reflection" onClick={() => addIssue()}><Icon name="plus" size={17}/> Add an opportunity</button>}</div></section></div></fieldset>
    <div className="editor-submit">
      {error && <div className="error-banner editor-save-feedback" role="alert" tabIndex={-1} ref={errorRef}>{error}</div>}
      {slowSave && <p className="editor-save-pending" role="status">Still saving to your workspace. Your draft is safe—keep this page open.</p>}
      {!readOnly && <div><button type="submit" className="button button-primary" disabled={submitting}>{submitting ? <span className="spinner"/> : null}{submitting ? 'Saving your reflection…' : 'Finish Retrospective'}{!submitting && <Icon name="arrow" size={17}/>}</button></div>}</div>
  </form>
    {deleteTarget && <Modal title={deleteTarget.type === 'win' ? 'Delete this win?' : deleteTarget.type === 'issue' ? 'Delete this improvement?' : 'Delete this action item?'} onClose={() => setDeleteTarget(null)} className="confirm-modal">
      <h2>{deleteTarget.type === 'win' ? 'Delete this win?' : deleteTarget.type === 'issue' ? 'Delete this improvement?' : 'Delete this action item?'}</h2>
      <p>{deleteTarget.type === 'issue' ? 'This will remove the improvement and its action items from your draft.' : 'This will remove the item from your draft.'}</p>
      <div className="confirm-actions"><button type="button" className="button button-secondary" onClick={() => setDeleteTarget(null)}>Cancel</button><button type="button" className="button button-danger" onClick={confirmDelete}>Delete</button></div>
    </Modal>}
  </>;
}
function Review({ retro, onClose }) {
  return <Modal title={`${D.sessionTitle(retro)} retrospective`} onClose={onClose} className="review-modal"><div className="review-header"><span className="eyebrow">RETROSPECTIVE RECAP</span><h2>{D.sessionTitle(retro)}<span className="heading-dot">.</span></h2><div><TeamBadge team={retro.team}/><span className="review-date"><Icon name="calendar" size={14}/>{D.formatDate(retro.date)}</span></div></div><div className="review-columns"><section className="review-wins"><h3><span className="review-emoji" aria-hidden="true">🙂</span> What went well <span className="review-count">{retro.whatWentWell?.length || 0}</span></h3>{(retro.whatWentWell || []).map((item, i) => <div className={`review-note ${item.votes >= 5 ? 'is-popular' : ''}`} key={i}>{item.votes >= 5 && <PopularNoteEffect/>}<p>{typeof item === 'string' ? item : item.text}</p>{item.votes > 0 && <span className="review-votes"><Icon name="spark" size={13}/> {item.votes} vote{item.votes !== 1 ? 's' : ''}</span>}</div>)}</section><section className="review-ideas"><h3><span className="review-emoji" aria-hidden="true">💡</span> What could improve <span className="review-count">{retro.negativePoints?.length || 0}</span></h3>{!retro.negativePoints?.length && <p className="review-no-issues">A smooth sprint. Keep the good things going.</p>}{(retro.negativePoints || []).map((point, i) => <div className="review-note" key={i}><p>{typeof point === 'string' ? point : point.issue}</p>{point.votes > 0 && <span className="review-votes"><Icon name="spark" size={13}/> {point.votes} vote{point.votes !== 1 ? 's' : ''}</span>}{point.actionItems?.filter(a => a.trim()).length > 0 && <div className="review-actions"><span>NEXT STEPS</span>{point.actionItems.filter(a => a.trim()).map((action, j) => <p key={j}><Icon name="arrow" size={14}/>{action}</p>)}</div>}</div>)}</section></div></Modal>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
