// desktop.jsx — Chrono for Mac, full-window calendar workspace

function AIEmptyState({ theme }) {
  return (
    <div style={{
      flex: 1, minHeight: 320,
      display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
      textAlign: 'center', padding: '40px 24px', gap: 14,
    }}>
      <div style={{
        width: 56, height: 56, borderRadius: 16,
        background: `radial-gradient(circle at 30% 30%, ${theme.accentColor}, ${theme.accentColor}55)`,
        boxShadow: theme.accentGlow,
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: '#fff',
      }}><Icon.sparkle s={22}/></div>
      <div style={{
        fontFamily: ChronoTokens.font.display, fontSize: 20, fontWeight: 700,
        color: theme.text, letterSpacing: '-0.02em',
      }}>Ask Chrono anything</div>
      <div style={{
        fontFamily: ChronoTokens.font.ui, fontSize: 13.5, color: theme.textDim,
        lineHeight: 1.5, maxWidth: 260,
      }}>Plan your week, block focus time, or move things around. Start by typing below.</div>
    </div>
  );
}

function ChronoDesktop({ theme, personality, initialView, fullBleed }) {
  useEvents();
  const [active, setActive] = React.useState(initialView || 'week');
  // Allow the guided tutorial to drive the active view.
  useTutorialView(React.useCallback((v) => { if (v) setActive(v); }, []));
  // Assistant popout is hidden by default — the user opens it from the
  // title-bar icon. The guided tour forces it open (and closed on exit) via
  // the tutorial-AI channel so new users see it during onboarding.
  const [aiOpen, setAiOpen] = React.useState(false);
  useTutorialAI(React.useCallback((open) => setAiOpen(open), []));
  // When the Trips planner finishes, it jumps the Week view to the trip's
  // first week so the user lands right on the new itinerary.
  const [weekJump, setWeekJump] = React.useState(null);
  const goToWeek = React.useCallback((wkOff) => { setWeekJump(wkOff); setActive('week'); }, []);
  // Top-bar search: jump the Week view to a found event and flash it.
  const [searchTarget, setSearchTarget] = React.useState(null);
  const goToEvent = React.useCallback((off, ev) => {
    const weekStartsOn = (window.settingsStore && window.settingsStore.get().weekStartsOn) || 0;
    const wkStart = off - ((dateAtOffset(off).getDay() - weekStartsOn + 7) % 7);
    setWeekJump(wkStart);
    setActive('week');
    setSearchTarget({ ev, off, ts: Date.now() });
  }, []);
  const [selected, setSelected] = React.useState(null);
  const [composerOpen, setComposerOpen] = React.useState(false);
  const [composerSeed, setComposerSeed] = React.useState(null);
  const [paywallOpen, setPaywallOpen] = React.useState(false);

  React.useEffect(() => { if (initialView) setActive(initialView); }, [initialView]);

  const openComposer = (seed = null) => { setComposerSeed(seed); setComposerOpen(true); };
  const closeComposer = () => setComposerOpen(false);

  return (
    <div style={{
      width: fullBleed ? '100%' : 1280,   // 100% of #root (virtual viewport) — 100vw would
      height: fullBleed ? '100%' : 800,   // overflow under the dynamic UI zoom in Chrono.html
      borderRadius: fullBleed ? 0 : 14,
      overflow: 'hidden', position: 'relative',
      background: theme.bg0, color: theme.text,
      boxShadow: fullBleed ? 'none' : '0 0 0 1px rgba(0,0,0,0.4), 0 40px 80px rgba(0,0,0,0.55)',
      fontFamily: ChronoTokens.font.ui,
      display: 'grid', gridTemplateColumns: '232px 1fr', gridTemplateRows: '40px 1fr',
    }}>
      {/* Title bar */}
      <DesktopTitleBar theme={theme} aiOpen={aiOpen} onToggleAI={()=>setAiOpen(o=>!o)} onSearchSelect={goToEvent}/>
      {/* Sidebar */}
      <DesktopSidebar theme={theme} active={active} setActive={setActive}/>
      {/* Main */}
      <div style={{ position: 'relative', overflow: 'hidden', display: 'grid', gridTemplateColumns: aiOpen ? '1fr 360px' : '1fr 0', gridTemplateRows: '1fr', minHeight: 0 }}>
        <div style={{ position: 'relative', overflow: 'hidden', minHeight: 0 }}>
          <DesktopAmbient theme={theme}/>
          <div key={active} className="chrono-page" style={{ position: 'absolute', inset: 0 }}>
            {active === 'week' && <DesktopWeek theme={theme} onSelect={setSelected} onNewEvent={openComposer} initialWeekOff={weekJump} highlightTarget={searchTarget}/>}
            {active === 'month' && <DesktopMonth theme={theme} onSelect={setSelected} onNewEvent={openComposer}/>}
            {active === 'focus' && <DesktopFocus theme={theme}/>}
            {active === 'settings' && <DesktopSettings theme={theme}/>}
            {active === 'ai' && <DesktopAIFull theme={theme} personality={personality}/>}
            {active === 'categories' && <DesktopCategories theme={theme}/>}
            {active === 'trips' && <DesktopTrips theme={theme} personality={personality} onGoToWeek={goToWeek}/>}
            {active === 'canvas' && <DesktopCanvas theme={theme}/>}
            {active === 'profile' && <DesktopProfile theme={theme} onUpgrade={()=>setPaywallOpen(true)}/>}
          </div>
        </div>
        {aiOpen && active !== 'ai' && (
          <DesktopAIPanel theme={theme} personality={personality} onClose={()=>setAiOpen(false)}/>
        )}
      </div>
      {selected && <DesktopEventDetails theme={theme} ev={selected} onClose={()=>setSelected(null)} onEdit={()=>{ openComposer(selected); setSelected(null); }}/>}
      {composerOpen && <DesktopEventComposer theme={theme} seed={composerSeed} onClose={closeComposer}/>}
      {paywallOpen && <DesktopPaywall theme={theme} onClose={()=>setPaywallOpen(false)}/>}
      {/* One-time beta welcome, shown once the tutorial is complete */}
      <BetaNotice theme={theme}/>
      {/* Global futurism overlay for desktop */}
      <FuturisticLayer theme={theme} intensity={theme.futurism}/>
    </div>
  );
}

// One-time "welcome to the beta" notice. Appears once the user has finished
// onboarding (so it lands right after the tutorial), then never again.
function BetaNotice({ theme }) {
  const onboarding = useOnboarding();
  if (!onboarding.done || onboarding.betaNoticeSeen) return null;
  const dismiss = () => setOnboarding({ betaNoticeSeen: true });
  return (
    <div onClick={dismiss} style={{
      position: 'absolute', inset: 0, zIndex: 80,
      background: 'rgba(0,0,0,0.55)', backdropFilter: 'blur(12px)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      animation: 'chrono-backdrop-in 200ms ease both',
    }}>
      <div onClick={e=>e.stopPropagation()} style={{
        width: 440, padding: 28, borderRadius: 16,
        background: theme.isDark ? 'rgba(20,22,32,0.97)' : 'rgba(255,255,255,0.99)',
        border: `0.5px solid ${theme.accentColor}55`,
        boxShadow: theme.isDark
          ? `0 30px 80px rgba(0,0,0,0.6), 0 0 60px ${theme.accentColor}33`
          : '0 30px 80px rgba(10,12,20,0.2)',
        animation: `chrono-modal-in 300ms ${Spring.std} both`,
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 14 }}>
          <div style={{
            width: 40, height: 40, borderRadius: 11,
            background: `radial-gradient(circle at 30% 30%, ${theme.accentColor}, ${theme.accentColor}66)`,
            boxShadow: theme.accentGlow, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: '#fff',
          }}><Icon.sparkle s={18}/></div>
          <div>
            <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 10, fontWeight: 700, letterSpacing: '0.18em', textTransform: 'uppercase', color: theme.accentColor }}>Private beta</div>
            <div style={{ fontFamily: ChronoTokens.font.display, fontSize: 20, fontWeight: 800, letterSpacing: '-0.02em' }}>Welcome to the Chrono beta</div>
          </div>
        </div>
        <div style={{ color: theme.textDim, fontSize: 13.5, lineHeight: 1.55, marginBottom: 18 }}>
          You're testing an early build, so expect a few rough edges. Every account has <b style={{ color: theme.text }}>Pro unlocked</b> while we test.
          Hit a bug or have an idea? Use <b style={{ color: theme.text }}>Send feedback</b> on your Profile — it goes straight to the team.
        </div>
        <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
          <DeskBtn theme={theme} primary onClick={dismiss}>Let's go</DeskBtn>
        </div>
      </div>
    </div>
  );
}

// Top-bar search — finds events by title/note/location/category. Selecting a
// result jumps the Week view to that event and flashes it (see goToEvent).
function DesktopSearch({ theme, onSelect }) {
  const [q, setQ] = React.useState('');
  const [open, setOpen] = React.useState(false);
  const [hi, setHi] = React.useState(0);
  useEvents(); // refresh results when the calendar changes
  const wrapRef = React.useRef(null);

  React.useEffect(() => {
    const onDoc = (e) => { if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, []);

  const term = q.trim().toLowerCase();
  const results = [];
  if (term) {
    for (const k of Object.keys(EVENTS_BY_OFFSET)) {
      const off = Number(k);
      for (const e of EVENTS_BY_OFFSET[k]) {
        const hay = `${e.title || ''} ${e.note || ''} ${e.location || ''} ${e.category || ''}`.toLowerCase();
        if (hay.includes(term)) results.push({ off, ev: e });
      }
    }
    // Nearest to today first, then chronological.
    results.sort((a, b) => Math.abs(a.off) - Math.abs(b.off) || a.off - b.off);
  }
  const shown = results.slice(0, 8);
  const dot = (c) => (ChronoTokens.events[c] || ChronoTokens.events.grey).glow;

  const choose = (r) => { if (!r) return; onSelect && onSelect(r.off, r.ev); setQ(''); setOpen(false); };
  const onKey = (e) => {
    if (e.key === 'Escape') { setQ(''); setOpen(false); e.currentTarget.blur(); }
    else if (e.key === 'ArrowDown') { e.preventDefault(); setHi(h => Math.min(h + 1, shown.length - 1)); }
    else if (e.key === 'ArrowUp')   { e.preventDefault(); setHi(h => Math.max(h - 1, 0)); }
    else if (e.key === 'Enter')     { choose(shown[hi] || shown[0]); }
  };

  return (
    <div ref={wrapRef} style={{ position: 'relative', width: 280, WebkitAppRegion: 'no-drag' }}>
      <div style={{
        display: 'flex', alignItems: 'center', gap: 8, padding: '4px 10px', borderRadius: 8,
        background: theme.isDark ? 'rgba(255,255,255,0.05)' : 'rgba(10,12,20,0.05)',
        border: `0.5px solid ${open && term ? theme.accentColor + '88' : theme.hairline}`,
      }}>
        <Icon.search s={13}/>
        <input value={q}
          onChange={e => { setQ(e.target.value); setOpen(true); setHi(0); }}
          onFocus={() => setOpen(true)}
          onKeyDown={onKey}
          placeholder="Search events…"
          style={{
            flex: 1, minWidth: 0, border: 'none', outline: 'none', background: 'transparent',
            color: theme.text, fontFamily: ChronoTokens.font.ui, fontSize: 12,
          }}/>
        {q ? (
          <button onClick={() => { setQ(''); setOpen(false); }} className="chrono-tap" title="Clear" style={{
            border: 'none', background: 'transparent', cursor: 'pointer', color: theme.textFaint,
            fontSize: 15, lineHeight: 1, padding: 0,
          }}>×</button>
        ) : (
          <span style={{
            fontFamily: ChronoTokens.font.mono, fontSize: 10, fontWeight: 600, color: theme.textFaint,
            padding: '1px 5px', borderRadius: 3,
            background: theme.isDark ? 'rgba(255,255,255,0.06)' : 'rgba(10,12,20,0.06)',
          }}>⌘K</span>
        )}
      </div>
      {open && term && (
        <div className="chrono-scrollbar" style={{
          position: 'absolute', top: 'calc(100% + 6px)', left: 0, right: 0, zIndex: 40,
          maxHeight: 340, overflowY: 'auto', padding: 6,
          background: theme.isDark ? 'rgba(20,22,32,0.98)' : 'rgba(255,255,255,0.99)',
          border: `0.5px solid ${theme.hairline}`, borderRadius: 10,
          boxShadow: '0 18px 50px rgba(0,0,0,0.45)',
        }}>
          {shown.length === 0 ? (
            <div style={{ padding: '12px 10px', fontSize: 12.5, color: theme.textFaint }}>No events match “{q}”.</div>
          ) : shown.map((r, i) => {
            const d = dateAtOffset(r.off);
            const when = `${DAY_NAMES[d.getDay()]}, ${MONTH_NAMES[d.getMonth()].slice(0, 3)} ${d.getDate()}`;
            return (
              <button key={i} className="chrono-tap" onMouseEnter={() => setHi(i)} onClick={() => choose(r)} style={{
                width: '100%', textAlign: 'left', cursor: 'pointer',
                display: 'flex', alignItems: 'center', gap: 10, padding: '8px 10px',
                border: 'none', borderRadius: 7,
                background: i === hi ? (theme.isDark ? 'rgba(255,255,255,0.06)' : 'rgba(10,12,20,0.05)') : 'transparent',
              }}>
                <span style={{ width: 8, height: 8, borderRadius: 999, flex: 'none', background: dot(r.ev.color), boxShadow: `0 0 6px ${dot(r.ev.color)}` }}/>
                <span style={{ flex: 1, minWidth: 0, fontSize: 13, color: theme.text, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{r.ev.title || '(untitled)'}</span>
                <span style={{ fontFamily: ChronoTokens.font.mono, fontSize: 10.5, color: theme.textFaint, flex: 'none' }}>{when} · {fmtTime(r.ev.start)}</span>
              </button>
            );
          })}
        </div>
      )}
    </div>
  );
}

function DesktopTitleBar({ theme, aiOpen, onToggleAI, onSearchSelect }) {
  return (
    <div style={{
      gridColumn: '1 / -1',
      display: 'flex', alignItems: 'center', gap: 12,
      padding: '0 14px', position: 'relative', zIndex: 10,
      background: theme.isDark ? 'rgba(14,16,24,0.85)' : 'rgba(248,248,252,0.92)',
      borderBottom: `0.5px solid ${theme.hairline}`,
      backdropFilter: 'blur(20px)',
      WebkitAppRegion: 'drag',
    }}>
      <div style={{ display: 'flex', gap: 8 }}>
        {['#FF5F57','#FEBC2E','#28C840'].map((c, i) => (
          <div key={i} style={{
            width: 12, height: 12, borderRadius: 999, background: c,
            border: '0.5px solid rgba(0,0,0,0.18)',
          }}/>
        ))}
      </div>
      <div style={{ flex: 1, display: 'flex', alignItems: 'center', gap: 8 }}>
        <div style={{ width: 18 }}/>
        <ChronoLogo size={16} color={theme.accentColor}/>
        <div style={{
          fontFamily: ChronoTokens.font.display, fontSize: 13, fontWeight: 600,
          color: theme.text, letterSpacing: '-0.01em',
        }}>Chrono</div>
        <div style={{
          fontFamily: ChronoTokens.font.mono, fontSize: 10, fontWeight: 500,
          color: theme.textFaint, letterSpacing: '0.1em', textTransform: 'uppercase',
          padding: '2px 6px', borderRadius: 4,
          background: theme.isDark ? 'rgba(255,255,255,0.04)' : 'rgba(10,12,20,0.04)',
        }}>v3.2 · Pro</div>
      </div>
      {/* Search — left of the AI button; finds events and flashes them on the Week view */}
      <DesktopSearch theme={theme} onSelect={onSearchSelect}/>
      {/* Assistant toggle — a labeled "AI" pill in a signature violet so it
          reads as the AI assistant, distinct from the accent-colored "+"
          manual event button beside it. */}
      {onToggleAI && (() => {
        const v = ChronoTokens.events.purple; // AI signature color
        return (
          <button className="chrono-tap chrono-btn" onClick={onToggleAI} style={{
            appearance: 'none', cursor: 'pointer',
            marginLeft: 8, height: 26, padding: '0 9px', borderRadius: 8,
            display: 'inline-flex', alignItems: 'center', gap: 5,
            background: aiOpen
              ? `linear-gradient(135deg, ${v.base}, ${v.glow})`
              : (theme.isDark ? `${v.glow}1A` : `${v.glow}12`),
            border: `0.5px solid ${aiOpen ? 'transparent' : v.glow + '55'}`,
            color: aiOpen ? '#fff' : v.glow,
            boxShadow: aiOpen ? `0 0 12px ${v.glow}66` : 'none',
          }} title={aiOpen ? 'Hide AI assistant' : 'Show AI assistant'}>
            <Icon.ai s={13}/>
            <span style={{
              fontFamily: ChronoTokens.font.mono, fontSize: 10.5, fontWeight: 700,
              letterSpacing: '0.06em',
            }}>AI</span>
          </button>
        );
      })()}
    </div>
  );
}

function DesktopAmbient({ theme }) {
  if (theme.bg === 'true-black') return null;
  return (
    <>
      <div style={{
        position: 'absolute', top: -200, left: 80, width: 600, height: 600,
        borderRadius: '50%',
        background: `radial-gradient(circle, ${theme.accentColor}${theme.isDark ? '22' : '0E'} 0%, transparent 65%)`,
        filter: 'blur(80px)', pointerEvents: 'none', zIndex: 0,
        animation: 'chrono-orb-1 20s ease-in-out infinite',
      }}/>
      <div style={{
        position: 'absolute', bottom: -150, right: 100, width: 480, height: 480,
        borderRadius: '50%',
        background: `radial-gradient(circle, ${theme.isDark ? '#7A5AF8' : '#A78BFA'}${theme.isDark ? '1A' : '08'} 0%, transparent 65%)`,
        filter: 'blur(80px)', pointerEvents: 'none', zIndex: 0,
        animation: 'chrono-orb-2 24s ease-in-out infinite',
      }}/>
    </>
  );
}

// ── Sidebar ───────────────────────────────────────────────────────────────
function DesktopSidebar({ theme, active, setActive }) {
  const items = [
    { id: 'week',       label: 'Week',       icon: <Icon.cal s={16}/> },
    { id: 'month',      label: 'Month',      icon: <Icon.cal s={16}/> },
    { id: 'focus',      label: 'Focus',      icon: <Icon.focus s={16}/> },
    { id: 'trips',      label: 'Trips',      icon: <Icon.pin s={16}/> },
    { id: 'ai',         label: 'Assistant',  icon: <Icon.ai s={16}/> },
    { id: 'categories', label: 'Categories', icon: <Icon.tag s={16}/> },
    { id: 'canvas',     label: 'Canvas LMS', icon: <Icon.cal s={16}/> },
    { id: 'profile',    label: 'Profile',    icon: <Icon.user s={16}/> },
    { id: 'settings',   label: 'Settings',   icon: <Icon.settings s={16}/> },
  ];
  const [catsOpen, setCatsOpen] = React.useState(true);
  // Live per-category counts — walks the event store and re-renders on any
  // event mutation (manual add/edit/delete or AI-created), so the sidebar
  // tallies stay in sync with what's actually on the calendar.
  const catCounts = useCategories();
  return (
    <div data-tutorial-id="sidebar" style={{
      borderRight: `0.5px solid ${theme.hairline}`,
      background: theme.isDark ? 'rgba(10,12,20,0.6)' : 'rgba(248,248,252,0.7)',
      backdropFilter: 'blur(40px)',
      display: 'flex', flexDirection: 'column',
      padding: '14px 10px', gap: 2,
      position: 'relative', zIndex: 5,
    }}>
      {/* Profile chip */}
      <button className="chrono-tap" style={{
        appearance: 'none', cursor: 'pointer', textAlign: 'left',
        display: 'flex', alignItems: 'center', gap: 10,
        padding: '8px 10px', borderRadius: 10,
        background: 'transparent', border: 'none', color: theme.text,
        marginBottom: 10,
      }}>
        <GradientBlob seed="you" size={28}/>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 13, fontWeight: 600, letterSpacing: '-0.01em' }}>Your name</div>
          <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 10, color: theme.accentColor, fontWeight: 700, letterSpacing: '0.04em' }}>PRO · BETA</div>
        </div>
        <Icon.fwd s={12}/>
      </button>

      {/* Categories — top, collapsible, default expanded */}
      <button className="chrono-tap" onClick={()=>setCatsOpen(o=>!o)} style={{
        appearance: 'none', cursor: 'pointer', border: 'none', background: 'transparent',
        display: 'flex', alignItems: 'center', gap: 6,
        padding: '4px 10px 6px', textAlign: 'left',
        fontFamily: ChronoTokens.font.mono, fontSize: 9.5, fontWeight: 600,
        color: theme.textFaint, letterSpacing: '0.14em', textTransform: 'uppercase',
      }}>
        <span style={{
          display: 'inline-flex', transition: 'transform 200ms ease',
          transform: catsOpen ? 'rotate(90deg)' : 'rotate(0deg)',
          color: theme.textFaint,
        }}><Icon.fwd s={9}/></span>
        <span style={{ flex: 1 }}>Categories</span>
        <span style={{
          fontSize: 9, color: theme.textFaint, opacity: 0.7,
        }}>{CATEGORIES.length}</span>
      </button>
      {catsOpen && (
        <div style={{ animation: 'chrono-fade-in 220ms ease both', marginBottom: 6 }}>
          {catCounts.map(c => {
            const ec = ChronoTokens.events[c.color];
            return (
              <div key={c.id} style={{
                display: 'flex', alignItems: 'center', gap: 10,
                padding: '5px 10px',
                fontFamily: ChronoTokens.font.ui, fontSize: 12.5, fontWeight: 500,
                color: theme.textDim,
              }}>
                <span style={{
                  width: 9, height: 9, borderRadius: 3,
                  background: `linear-gradient(135deg, ${ec.base}, ${ec.glow})`,
                  boxShadow: theme.isDark ? `0 0 6px ${ec.glow}88` : 'none',
                }}/>
                <span style={{ flex: 1 }}>{c.name}</span>
                <span style={{ fontFamily: ChronoTokens.font.mono, fontSize: 10, color: theme.textFaint }}>{c.count}</span>
              </div>
            );
          })}
          <button className="chrono-tap" onClick={()=>setActive('categories')} style={{
            appearance: 'none', cursor: 'pointer', border: 'none', background: 'transparent',
            display: 'flex', alignItems: 'center', gap: 8,
            padding: '5px 10px', width: '100%', textAlign: 'left',
            fontFamily: ChronoTokens.font.ui, fontSize: 12, fontWeight: 500,
            color: theme.textFaint,
          }}>
            <Icon.plus s={11}/> {CATEGORIES.length ? 'New category' : 'Create your first category'}
          </button>
        </div>
      )}

      <SidebarLabel theme={theme} style={{ marginTop: 10 }}>Workspace</SidebarLabel>
      {items.map(it => {
        const a = active === it.id;
        return (
          <button key={it.id} className="chrono-tap chrono-side" onClick={()=>setActive(it.id)}
            style={{
              appearance: 'none', cursor: 'pointer', textAlign: 'left',
              display: 'flex', alignItems: 'center', gap: 10,
              padding: '7px 10px', borderRadius: 8,
              background: a ? (theme.isDark ? `${theme.accentColor}1F` : `${theme.accentColor}10`) : 'transparent',
              color: a ? theme.accentColor : theme.text,
              border: 'none',
              fontFamily: ChronoTokens.font.ui, fontSize: 13, fontWeight: 500,
              letterSpacing: '-0.005em',
              boxShadow: a ? `inset 0 0 0 0.5px ${theme.accentColor}55` : 'none',
            }}>
            <span style={{ display: 'inline-flex', color: a ? theme.accentColor : theme.textDim }}>{it.icon}</span>
            <span style={{ flex: 1 }}>{it.label}</span>
            {it.badge && <span style={{
              fontFamily: ChronoTokens.font.mono, fontSize: 10, fontWeight: 700,
              padding: '1px 6px', borderRadius: 999,
              background: a ? `${theme.accentColor}33` : (theme.isDark ? 'rgba(255,255,255,0.06)' : 'rgba(10,12,20,0.06)'),
              color: a ? theme.accentColor : theme.textDim,
            }}>{it.badge}</span>}
          </button>
        );
      })}

      <div style={{ flex: 1 }}/>

    </div>
  );
}

function SidebarLabel({ theme, children, style }) {
  return (
    <div style={{
      fontFamily: ChronoTokens.font.mono, fontSize: 9.5, fontWeight: 600,
      color: theme.textFaint, letterSpacing: '0.14em', textTransform: 'uppercase',
      padding: '4px 10px 6px',
      ...style,
    }}>{children}</div>
  );
}

// ── Week View ─────────────────────────────────────────────────────────────
// Compact time label for narrow event pills — "9:30 AM" → "9:30a", "6:00 PM"
// → "6p". Saves horizontal space so the title shows in side-by-side columns.
function fmtTimeShort(hhmm) {
  const [h, m] = String(hhmm).split(':').map(Number);
  const period = h >= 12 ? 'p' : 'a';
  const h12 = h % 12 || 12;
  return m === 0 ? `${h12}${period}` : `${h12}:${String(m).padStart(2,'0')}${period}`;
}

// Lay out a day's events into non-overlapping side-by-side columns (à la
// Google Calendar). Two events "conflict" if their VISUAL spans overlap —
// visual span = [start, max(realEnd, start + minMinutes)] — so a tiny 5-min
// event whose min-height pill would cover the next block gets its own column
// instead of being drawn on top of it. Returns Map(index → {col, cols, top,
// dispH, compact}).
function layoutDayEvents(evs, hourH, hour0, minHeightPx) {
  const pxPerMin = hourH / 60;
  const minMin = minHeightPx / pxPerMin;
  const items = evs.map((e, i) => {
    const s = tm(e.start), en = tm(e.end);
    return { i, s, en, vEnd: Math.max(en, s + minMin) };
  }).sort((a, b) => a.s - b.s || a.vEnd - b.vEnd);

  const out = new Map();
  let cluster = [], clusterEnd = -Infinity;
  const flush = () => {
    const colEnds = []; // running vEnd per column
    for (const it of cluster) {
      let c = 0;
      while (c < colEnds.length && it.s < colEnds[c]) c++;
      colEnds[c] = it.vEnd;
      it.col = c;
    }
    const cols = colEnds.length;
    for (const it of cluster) {
      const top = (it.s - hour0 * 60) / 60 * hourH;
      const dispH = Math.max(minHeightPx, (it.en - it.s) / 60 * hourH - 2);
      out.set(it.i, { col: it.col, cols, top, dispH, compact: dispH < 40 });
    }
    cluster = [];
  };
  for (const it of items) {
    if (cluster.length && it.s >= clusterEnd) { flush(); clusterEnd = -Infinity; }
    cluster.push(it);
    clusterEnd = Math.max(clusterEnd, it.vEnd);
  }
  flush();
  return out;
}

function DesktopWeek({ theme, onSelect, onNewEvent, initialWeekOff, highlightTarget }) {
  useEvents();
  const settings = useSettings();
  const MIN_HOUR_H = 64;
  const HOURS = Array.from({length: 24}, (_, i) => i); // 12am – 12am (full 24h)

  // Offset (from CHRONO_TODAY) of the first column of today's week, honoring the
  // "Week starts on" setting: 0=Sunday, 1=Monday. Rolls back to whichever weekday
  // the user chose as the start.
  const currentWeekStartOffset = -((CHRONO_TODAY.getDay() - settings.weekStartsOn + 7) % 7);
  const [weekStart, setWeekStart] = React.useState(
    initialWeekOff != null ? initialWeekOff : currentWeekStartOffset);
  // Jump to a specific week when asked (e.g. the Trips planner lands here on the
  // trip's first week).
  React.useEffect(() => { if (initialWeekOff != null) setWeekStart(initialWeekOff); }, [initialWeekOff]);
  // When "Week starts on" changes, re-align the displayed grid to the new start
  // weekday while keeping the user on the same week they were viewing.
  const prevWeekStartsOn = React.useRef(settings.weekStartsOn);
  React.useEffect(() => {
    const oldOff = -((CHRONO_TODAY.getDay() - prevWeekStartsOn.current + 7) % 7);
    const newOff = -((CHRONO_TODAY.getDay() - settings.weekStartsOn + 7) % 7);
    if (oldOff !== newOff) setWeekStart(ws => ws + (newOff - oldOff));
    prevWeekStartsOn.current = settings.weekStartsOn;
  }, [settings.weekStartsOn]);
  const days = [0,1,2,3,4,5,6].map(i => weekStart + i);

  // Search "find" flash: when highlightTarget changes, scroll the matching
  // event into view and pulse it for a couple of seconds so the user spots it.
  const [flashEv, setFlashEv] = React.useState(null);
  const flashRef = React.useRef(null);
  const flashClearRef = React.useRef(null);
  React.useEffect(() => {
    if (!highlightTarget || !highlightTarget.ev) return;
    setFlashEv(highlightTarget.ev);
    const scrollT = setTimeout(() => {
      const el = flashRef.current;
      if (el && el.scrollIntoView) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
    }, 90); // let the matched element mount (and the week switch, if any) before scrolling
    if (flashClearRef.current) clearTimeout(flashClearRef.current);
    flashClearRef.current = setTimeout(() => setFlashEv(null), 2600);
    return () => { clearTimeout(scrollT); if (flashClearRef.current) clearTimeout(flashClearRef.current); };
  }, [highlightTarget && highlightTarget.ts]);

  // Adaptive hour height: floor(scrollerHeight/24) + 1 guarantees content > scroller
  // (always scrollable) AND fills the box on large viewports with a minimal residual.
  const scrollerRef = React.useRef(null);
  const [hourH, setHourH] = React.useState(MIN_HOUR_H);
  const didInitialScroll = React.useRef(false);
  React.useLayoutEffect(() => {
    const el = scrollerRef.current;
    if (!el) return;
    const measure = () => {
      const avail = el.clientHeight;
      if (avail <= 0) return;
      const next = Math.max(MIN_HOUR_H, Math.floor(avail / 24) + 1);
      setHourH(prev => prev === next ? prev : next);
      if (!didInitialScroll.current) {
        el.scrollTop = 7 * next;
        didInitialScroll.current = true;
      }
    };
    measure();
    const ro = new ResizeObserver(measure);
    ro.observe(el);
    return () => ro.disconnect();
  }, []);

  // Header label: spans the week range. Single month → "June 2026"; cross-month → "Jun – Jul 2026".
  const firstDate = dateAtOffset(weekStart);
  const lastDate  = dateAtOffset(weekStart + 6);
  const weekLabel = firstDate.getMonth() === lastDate.getMonth()
    ? `${MONTH_NAMES[firstDate.getMonth()]} ${firstDate.getFullYear()}`
    : `${MONTH_NAMES[firstDate.getMonth()].slice(0,3)} – ${MONTH_NAMES[lastDate.getMonth()].slice(0,3)} ${lastDate.getFullYear()}`;
  // ISO-ish week number (counts weeks since Jan 1).
  const jan1 = new Date(firstDate.getFullYear(), 0, 1);
  const weekNum = Math.floor(((firstDate - jan1) / 86400000 + jan1.getDay()) / 7) + 1;

  const now = new Date();
  const nowMin = now.getHours()*60 + now.getMinutes();
  const nowOff = (nowMin - HOURS[0]*60) / 60 * hourH;
  const totalH = HOURS.length * hourH;

  // ── Drag-to-move & drag-to-resize ──────────────────────────────────────
  // Pointer math lives in stable callbacks that read live values from refs,
  // so the window listeners we attach/detach are the same instances (reliable
  // cleanup). The store is only mutated on mouse-up — during the drag we keep
  // a transient preview in dragRef and re-render a "ghost" block. Because the
  // commit goes through updateEventByRef, the next AI prompt's snapshot picks
  // up the new day/time automatically.
  const SNAP = 15, MAX_MIN = 1439;
  const gridRef = React.useRef(null);
  const dragRef = React.useRef(null);
  const [, forceDrag] = React.useReducer(x => x + 1, 0);
  const liveRef = React.useRef({});
  liveRef.current = { hourH, weekStart };
  const onSelectRef = React.useRef(onSelect);
  onSelectRef.current = onSelect;

  const onDragMove = React.useCallback((e) => {
    const d = dragRef.current; if (!d) return;
    const { hourH: hH, weekStart: wStart } = liveRef.current;
    // Under the dynamic UI zoom, clientX/Y and getBoundingClientRect are in
    // scaled (visual) px while hourH/colW are layout px — divide by the scale.
    const sc = window.__chronoScale || 1;
    const pxPerMin = hH / 60;
    const dyMin = Math.round((e.clientY - d.pointerY0) / sc / pxPerMin / SNAP) * SNAP;
    const dur = d.originEndMin - d.originStartMin;
    let min0 = d.originStartMin, min1 = d.originEndMin, off = d.originOff;
    if (d.mode === 'move') {
      min0 = Math.max(0, Math.min(MAX_MIN - dur, d.originStartMin + dyMin));
      min1 = min0 + dur;
      const grid = gridRef.current;
      if (grid) {
        const r = grid.getBoundingClientRect();
        const colW = (r.width / sc - 64) / 7;
        let ci = Math.floor(((e.clientX - r.left) / sc - 64) / colW);
        off = wStart + Math.max(0, Math.min(6, ci));
      }
    } else if (d.mode === 'resize-start') {
      min0 = Math.max(0, Math.min(d.originEndMin - SNAP, d.originStartMin + dyMin));
      min1 = d.originEndMin;
    } else { // resize-end
      min1 = Math.max(d.originStartMin + SNAP, Math.min(MAX_MIN, d.originEndMin + dyMin));
      min0 = d.originStartMin;
    }
    const moved = d.moved || Math.abs(e.clientX - d.pointerX0) > 3 || Math.abs(e.clientY - d.pointerY0) > 3;
    dragRef.current = { ...d, off, min0, min1, moved };
    forceDrag();
  }, []);

  const onDragUp = React.useCallback(() => {
    window.removeEventListener('mousemove', onDragMove);
    window.removeEventListener('mouseup', onDragUp);
    const d = dragRef.current; dragRef.current = null;
    if (d) {
      if (d.moved) {
        // Commit move/resize. updateEventByRef relocates across day arrays and
        // swaps in fresh start/end — keeps every category/note/priority field.
        updateEventByRef(d.ev, d.off, { ...d.ev, start: fmtHM(d.min0), end: fmtHM(d.min1) });
      } else {
        onSelectRef.current && onSelectRef.current(d.ev); // a plain click → open details
      }
    }
    forceDrag();
  }, [onDragMove]);

  const beginDrag = (e, ev, off, mode) => {
    e.preventDefault(); e.stopPropagation();
    dragRef.current = {
      ev, mode, originOff: off,
      originStartMin: tm(ev.start), originEndMin: tm(ev.end),
      pointerX0: e.clientX, pointerY0: e.clientY,
      off, min0: tm(ev.start), min1: tm(ev.end), moved: false,
    };
    window.addEventListener('mousemove', onDragMove);
    window.addEventListener('mouseup', onDragUp);
    forceDrag();
  };

  React.useEffect(() => () => {
    window.removeEventListener('mousemove', onDragMove);
    window.removeEventListener('mouseup', onDragUp);
  }, [onDragMove, onDragUp]);

  const dragging = dragRef.current;

  return (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 1,
      display: 'grid', gridTemplateRows: 'auto auto 1fr',
    }}>
      <DesktopHeader theme={theme} title="This week" eyebrow={`${weekLabel} · Week ${weekNum}`}
        right={<>
          <DeskBtn theme={theme} onClick={()=>setWeekStart(o => o - 7)}><Icon.back s={14}/></DeskBtn>
          <DeskBtn theme={theme} onClick={()=>setWeekStart(currentWeekStartOffset)}>Today</DeskBtn>
          <DeskBtn theme={theme} onClick={()=>setWeekStart(o => o + 7)}><Icon.fwd s={14}/></DeskBtn>
          <div style={{ width: 12 }}/>
          <span data-tutorial-id="new-event"><DeskBtn theme={theme} primary onClick={onNewEvent}><Icon.plus s={14}/> New event</DeskBtn></span>
        </>}
      />
      {/* Day header */}
      <div style={{
        display: 'grid', gridTemplateColumns: `64px repeat(7,1fr)`,
        borderBottom: `0.5px solid ${theme.hairline}`,
        padding: '0 24px',
      }}>
        <div></div>
        {days.map(off => {
          const d = dateAtOffset(off);
          const isToday = off === 0;
          const holiday = settings.showHolidays ? holidayForOffset(off) : null;
          return (
            <div key={off} style={{
              padding: '8px 6px',
              borderLeft: `0.5px solid ${theme.hairline}`,
              minWidth: 0,
            }}>
              <div style={{
                fontFamily: ChronoTokens.font.mono, fontSize: 10, fontWeight: 600,
                color: theme.textFaint, letterSpacing: '0.1em', textTransform: 'uppercase',
              }}>{DAY_FULL[d.getDay()].slice(0,3)}</div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 2, minWidth: 0 }}>
                <div style={{
                  flex: 'none',
                  width: 28, height: 28, borderRadius: 999,
                  display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                  background: isToday ? theme.accentColor : 'transparent',
                  color: isToday ? '#fff' : theme.text,
                  fontFamily: ChronoTokens.font.display, fontSize: 16, fontWeight: 700,
                  boxShadow: isToday ? theme.accentGlow : 'none',
                }}>{d.getDate()}</div>
                {holiday ? (
                  <span title={holiday} style={{
                    flex: 1, minWidth: 0,
                    fontFamily: ChronoTokens.font.ui, fontSize: 13, fontWeight: 700,
                    color: theme.accentColor, letterSpacing: '-0.01em',
                    background: `${theme.accentColor}1A`,
                    padding: '3px 8px', borderRadius: 999,
                    border: `0.5px solid ${theme.accentColor}33`,
                    whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
                  }}>{holiday}</span>
                ) : (
                  <span style={{ fontFamily: ChronoTokens.font.mono, fontSize: 11, color: theme.textDim }}>
                    {eventsForOffset(off).length} ev
                  </span>
                )}
              </div>
            </div>
          );
        })}
      </div>

      {/* Scroller */}
      <div ref={scrollerRef} data-tutorial-id="week-grid" className="chrono-scrollbar" style={{
        minHeight: 0, overflowY: 'auto', overflowX: 'hidden', padding: '10px 24px 0',
      }}>
        <div ref={gridRef} style={{
          display: 'grid', gridTemplateColumns: `64px repeat(7,1fr)`,
          gridTemplateRows: `repeat(24, ${hourH}px)`,
          position: 'relative',
          height: totalH,
          userSelect: dragging ? 'none' : 'auto',
          cursor: dragging ? (dragging.mode === 'move' ? 'grabbing' : 'ns-resize') : 'auto',
        }}>
          {/* Hour labels — one cell per hour row in column 1 */}
          {HOURS.map((h, i) => (
            <div key={`hl-${h}`} style={{
              gridColumn: 1, gridRow: i + 1,
              paddingRight: 12, textAlign: 'right',
              fontFamily: ChronoTokens.font.mono, fontSize: 10, fontWeight: 500,
              color: theme.textFaint,
            }}>
              <span style={{ display: 'block', transform: 'translateY(-6px)' }}>
                {fmtHour(h)}
              </span>
            </div>
          ))}

          {/* Day columns: one wrapper per day spanning all 24 rows */}
          {days.map((off, ci) => {
            const evs = eventsForOffset(off);
            const isToday = off === 0;
            // Side-by-side column layout so overlapping / clamped-tiny events
            // (e.g. 5-min breaks) sit beside their neighbours instead of on top.
            const MIN_EVENT_H = 18;
            const dayLayout = layoutDayEvents(evs, hourH, HOURS[0], MIN_EVENT_H);
            return (
              <div key={`col-${off}`} style={{
                gridColumn: ci + 2, gridRow: '1 / -1',
                position: 'relative',
                borderLeft: `0.5px solid ${theme.hairline}`,
              }}>
                {HOURS.map((h, i) => (
                  <div key={`hr-${h}`} style={{
                    height: hourH,
                    borderTop: i === 0 ? 'none' : `0.5px dashed ${theme.hairline}`,
                  }}/>
                ))}
                {evs.map((e, i) => {
                  const lay = dayLayout.get(i);
                  if (!lay) return null;
                  const { col, cols, top, dispH, compact } = lay;
                  if (top < 0 || top > totalH) return null;
                  const c = ChronoTokens.events[e.color] || ChronoTokens.events.grey;
                  // If this event is the one being dragged, fade it as a
                  // placeholder; the live position renders as a ghost below.
                  const isDragSource = dragging && dragging.ev === e && dragging.moved;
                  const isFlash = flashEv && e === flashEv;
                  const handleH = compact ? 5 : 7;
                  const leftPct = (col / cols) * 100;
                  const widthPct = (1 / cols) * 100;
                  return (
                    <div key={i} className="chrono-tap"
                      ref={isFlash ? flashRef : undefined}
                      onMouseDown={(ev)=>beginDrag(ev, e, off, 'move')} style={{
                      position: 'absolute',
                      left: `calc(${leftPct}% + 3px)`,
                      width: `calc(${widthPct}% - 6px)`,
                      top, height: dispH, boxSizing: 'border-box',
                      cursor: 'grab', textAlign: 'left',
                      zIndex: isFlash ? 9 : undefined,
                      animation: isFlash ? 'chrono-find-flash 1.2s ease-out 2' : undefined,
                      background: theme.isDark
                        ? `linear-gradient(135deg, ${c.base}33, ${c.glow}1F)`
                        : `linear-gradient(135deg, ${c.base}26, ${c.glow}14)`,
                      border: `0.5px solid ${c.base}55`,
                      borderLeft: `2.5px solid ${c.glow}`,
                      borderRadius: 6, padding: compact ? '0 6px' : '4px 8px',
                      display: compact ? 'flex' : 'block',
                      alignItems: compact ? 'center' : undefined,
                      gap: compact ? 5 : undefined,
                      backdropFilter: 'blur(8px)',
                      boxShadow: theme.isDark ? `0 2px 6px ${c.glow}33` : 'none',
                      overflow: 'hidden',
                      opacity: isDragSource ? 0.32 : 1,
                    }}>
                      {/* top resize handle */}
                      <div onMouseDown={(ev)=>beginDrag(ev, e, off, 'resize-start')} style={{
                        position: 'absolute', top: 0, left: 0, right: 0, height: handleH,
                        cursor: 'ns-resize', zIndex: 2,
                      }}/>
                      {compact ? (
                        // Single-line pill — title + time inline, like GCal's
                        // "break, 6pm" so a 5-min event is fully legible.
                        <>
                          <span style={{
                            fontSize: 11, fontWeight: 600, color: theme.isDark ? '#fff' : c.glow,
                            letterSpacing: '-0.01em', whiteSpace: 'nowrap', overflow: 'hidden',
                            textOverflow: 'ellipsis', minWidth: 0, pointerEvents: 'none',
                          }}>{e.title}</span>
                          {/* Inline time only when the pill is full-width — in
                              narrow side-by-side columns the title gets all the
                              room (time is still one click away). */}
                          {cols === 1 && <span style={{
                            fontFamily: ChronoTokens.font.mono, fontSize: 9,
                            color: theme.isDark ? `${c.base}EE` : c.glow, opacity: 0.8,
                            whiteSpace: 'nowrap', flex: 'none', pointerEvents: 'none',
                          }}>{fmtTimeShort(e.start)}</span>}
                        </>
                      ) : (
                        <>
                          <div style={{
                            fontSize: 11.5, fontWeight: 600, color: theme.isDark ? '#fff' : c.glow,
                            letterSpacing: '-0.01em', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
                            pointerEvents: 'none',
                          }}>{e.title}</div>
                          <div style={{
                            fontFamily: ChronoTokens.font.mono, fontSize: 9.5,
                            color: theme.isDark ? `${c.base}EE` : c.glow, opacity: 0.85, marginTop: 1,
                            pointerEvents: 'none',
                          }}>{fmtTime(e.start)}</div>
                        </>
                      )}
                      {/* bottom resize handle */}
                      <div onMouseDown={(ev)=>beginDrag(ev, e, off, 'resize-end')} style={{
                        position: 'absolute', bottom: 0, left: 0, right: 0, height: handleH,
                        cursor: 'ns-resize', zIndex: 2,
                      }}/>
                    </div>
                  );
                })}
                {/* Drag ghost — the live preview of the event being moved/resized,
                    rendered in whichever day column the pointer is over. */}
                {dragging && dragging.moved && dragging.off === off && (() => {
                  const gTop = (dragging.min0 - HOURS[0]*60) / 60 * hourH;
                  const gH = Math.max(28, (dragging.min1 - dragging.min0) / 60 * hourH - 2);
                  const gc = ChronoTokens.events[dragging.ev.color] || ChronoTokens.events.grey;
                  return (
                    <div style={{
                      position: 'absolute', left: 4, right: 4, top: gTop, height: gH,
                      background: theme.isDark
                        ? `linear-gradient(135deg, ${gc.base}55, ${gc.glow}40)`
                        : `linear-gradient(135deg, ${gc.base}45, ${gc.glow}2A)`,
                      border: `1px solid ${gc.glow}`,
                      borderLeft: `2.5px solid ${gc.glow}`,
                      borderRadius: 6, padding: '4px 8px',
                      boxShadow: `0 6px 18px ${gc.glow}66`,
                      overflow: 'hidden', pointerEvents: 'none', zIndex: 10,
                    }}>
                      <div style={{
                        fontSize: 11.5, fontWeight: 700, color: theme.isDark ? '#fff' : gc.glow,
                        whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
                      }}>{dragging.ev.title}</div>
                      <div style={{
                        fontFamily: ChronoTokens.font.mono, fontSize: 9.5,
                        color: theme.isDark ? '#fff' : gc.glow, opacity: 0.95, marginTop: 1,
                      }}>{fmtTime(fmtHM(dragging.min0))}–{fmtTime(fmtHM(dragging.min1))}</div>
                    </div>
                  );
                })()}
                {isToday && nowOff >= 0 && nowOff < totalH && (
                  <div style={{
                    position: 'absolute', left: -4, right: 0, top: nowOff,
                    height: 0, borderTop: `1.5px solid ${theme.accentColor}`,
                    boxShadow: `0 0 8px ${theme.accentColor}AA`,
                    zIndex: 5,
                  }}>
                    <div style={{
                      position: 'absolute', left: 0, top: -5, width: 10, height: 10,
                      borderRadius: 999, background: theme.accentColor,
                      boxShadow: theme.accentGlow,
                    }}>
                      <div style={{
                        position: 'absolute', inset: -2, borderRadius: 999,
                        background: theme.accentColor, opacity: 0.5,
                        animation: 'chrono-now-ping 2s ease-out infinite',
                      }}/>
                    </div>
                  </div>
                )}
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

function DesktopHeader({ theme, title, eyebrow, right }) {
  return (
    <div style={{
      padding: '20px 24px 16px',
      display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', gap: 16,
      position: 'relative', zIndex: 2,
    }}>
      <div>
        <div style={{
          fontFamily: ChronoTokens.font.mono, fontSize: 13, fontWeight: 700,
          color: theme.accentColor, letterSpacing: '0.12em', textTransform: 'uppercase',
          textShadow: theme.isDark ? `0 0 12px ${theme.accentColor}66` : 'none',
          marginBottom: 2,
        }}>{eyebrow}</div>
        <div style={{
          fontFamily: ChronoTokens.font.display, fontSize: 26, fontWeight: 700,
          color: theme.text, letterSpacing: '-0.025em', lineHeight: 1.1,
        }}>{title}</div>
      </div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>{right}</div>
    </div>
  );
}

function DeskBtn({ children, primary, theme, onClick }) {
  return (
    <button className="chrono-tap chrono-btn" onClick={onClick} style={{
      appearance: 'none', cursor: 'pointer',
      padding: '6px 12px', borderRadius: 8,
      border: `0.5px solid ${primary ? 'transparent' : theme.hairline}`,
      background: primary
        ? `linear-gradient(180deg, ${theme.accentColor}, ${theme.accentColor}DD)`
        : (theme.isDark ? 'rgba(255,255,255,0.04)' : '#fff'),
      color: primary ? '#fff' : theme.text,
      fontFamily: ChronoTokens.font.ui, fontSize: 12, fontWeight: 600,
      letterSpacing: '-0.005em',
      boxShadow: primary ? theme.accentGlow : (theme.isDark ? 'none' : '0 1px 2px rgba(0,0,0,0.04)'),
      display: 'inline-flex', alignItems: 'center', gap: 6,
    }}>{children}</button>
  );
}

// ── Month View (full bleed) ───────────────────────────────────────────────
function DesktopMonth({ theme, onSelect, onNewEvent }) {
  useEvents();
  const settings = useSettings();
  // monthDelta = months from current real month (0 = current).
  const [monthDelta, setMonthDelta] = React.useState(0);
  const todayRef = CHRONO_TODAY;
  const ref = new Date(todayRef.getFullYear(), todayRef.getMonth() + monthDelta, 1);
  const year = ref.getFullYear(), month = ref.getMonth();
  const first = new Date(year, month, 1);
  // Back up to the configured first-day-of-week (0=Sun, 1=Mon) before the 1st.
  const leadDays = (first.getDay() - settings.weekStartsOn + 7) % 7;
  const gridStart = new Date(first); gridStart.setDate(1 - leadDays);
  // Weekday header labels rotated to match the configured week start.
  const dowLabels = DAY_NAMES.map((_, i) => DAY_NAMES[(i + settings.weekStartsOn) % 7]);
  const cells = [];
  for (let i = 0; i < 42; i++) { // full 6-week grid → entire month always visible
    const d = new Date(gridStart); d.setDate(gridStart.getDate()+i);
    cells.push(d);
  }
  const [popKey, setPopKey] = React.useState(null); // index of cell with open popover
  const closeAll = () => setPopKey(null);
  React.useEffect(() => {
    if (popKey === null) return;
    const onKey = e => { if (e.key === 'Escape') closeAll(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [popKey]);

  return (
    <div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', zIndex: 1 }} onClick={closeAll}>
      <DesktopHeader theme={theme} eyebrow={`${MONTH_NAMES[month]} ${year}`} title="Month overview"
        right={<>
          <DeskBtn theme={theme} onClick={()=>setMonthDelta(d => d - 1)}><Icon.back s={14}/></DeskBtn>
          <DeskBtn theme={theme} onClick={()=>setMonthDelta(0)}>Today</DeskBtn>
          <DeskBtn theme={theme} onClick={()=>setMonthDelta(d => d + 1)}><Icon.fwd s={14}/></DeskBtn>
          <div style={{ width: 12 }}/>
          <span data-tutorial-id="new-event"><DeskBtn theme={theme} primary onClick={onNewEvent}><Icon.plus s={14}/> New event</DeskBtn></span>
        </>}
      />
      <div style={{
        display: 'grid', gridTemplateColumns: 'repeat(7,1fr)',
        padding: '6px 24px',
        fontFamily: ChronoTokens.font.mono, fontSize: 10, fontWeight: 600,
        color: theme.textFaint, letterSpacing: '0.12em', textTransform: 'uppercase',
        borderBottom: `0.5px solid ${theme.hairline}`,
      }}>
        {dowLabels.map(d => <div key={d} style={{ padding: '4px 8px' }}>{d}</div>)}
      </div>
      <div style={{
        flex: 1, display: 'grid', gridTemplateColumns: 'repeat(7,1fr)', gridAutoRows: '1fr',
        padding: '0 24px 24px',
        position: 'relative',
      }}>
        {cells.map((d, i) => {
          const off = offsetForDate(d);
          const inMonth = d.getMonth() === month;
          const isToday = off === 0;
          const evs = eventsForOffset(off);
          const holiday = settings.showHolidays ? holidayForDate(d) : null;
          const MAX_VIS = 2; // prioritize seeing the full month
          const visible = evs.slice(0, MAX_VIS);
          const hidden = evs.length - visible.length;
          const isPopOpen = popKey === i;
          // Anchor popover left/right based on column to avoid clipping
          const col = i % 7;
          const popRight = col >= 4;
          return (
            <div key={i} style={{
              position: 'relative',
              padding: '6px 6px',
              borderRight: (i+1) % 7 === 0 ? 'none' : `0.5px solid ${theme.hairline}`,
              borderTop: i >= 7 ? `0.5px solid ${theme.hairline}` : 'none',
              opacity: inMonth ? 1 : 0.4,
              minHeight: 0, overflow: 'visible',
              display: 'flex', flexDirection: 'column', gap: 3,
            }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 }}>
                <div style={{
                  flex: 'none',
                  display: 'inline-flex',
                  width: 20, height: 20, borderRadius: 999,
                  alignItems: 'center', justifyContent: 'center',
                  background: isToday ? theme.accentColor : 'transparent',
                  color: isToday ? '#fff' : theme.text,
                  fontFamily: ChronoTokens.font.display, fontSize: 11, fontWeight: 700,
                  boxShadow: isToday ? theme.accentGlow : 'none',
                }}>{d.getDate()}</div>
                {holiday && (
                  <span title={holiday} style={{
                    flex: 1, minWidth: 0,
                    fontFamily: ChronoTokens.font.ui, fontSize: 11.5, fontWeight: 700,
                    color: theme.accentColor, letterSpacing: '-0.01em',
                    background: `${theme.accentColor}1A`,
                    padding: '2px 6px', borderRadius: 4,
                    whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
                  }}>{holiday}</span>
                )}
              </div>
              {visible.map((e, j) => {
                const c = ChronoTokens.events[e.color];
                return (
                  <button key={j} className="chrono-tap"
                    onClick={(ev)=>{ev.stopPropagation(); onSelect(e);}} style={{
                    appearance: 'none', cursor: 'pointer', textAlign: 'left',
                    border: 'none', borderLeft: `2px solid ${c.glow}`,
                    background: theme.isDark ? `${c.base}1A` : `${c.base}10`,
                    color: theme.isDark ? '#fff' : c.glow,
                    padding: '1px 5px', borderRadius: 3,
                    fontFamily: ChronoTokens.font.ui, fontSize: 10, fontWeight: 600,
                    letterSpacing: '-0.005em', lineHeight: 1.35,
                    whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
                  }}>{fmtTime(e.start).replace(' ','')} {e.title}</button>
                );
              })}
              {hidden > 0 && (
                <button className="chrono-tap"
                  onClick={(ev)=>{ev.stopPropagation(); setPopKey(isPopOpen ? null : i);}}
                  style={{
                    appearance: 'none', cursor: 'pointer', textAlign: 'left',
                    border: 'none', background: 'transparent',
                    padding: '1px 5px', borderRadius: 3,
                    fontFamily: ChronoTokens.font.mono, fontSize: 9.5, fontWeight: 600,
                    color: isPopOpen ? theme.accentColor : theme.textDim,
                    letterSpacing: '0.02em',
                    display: 'flex', alignItems: 'center', gap: 3,
                  }}>
                  <span style={{
                    display: 'inline-flex', gap: 2,
                  }}>
                    {evs.slice(MAX_VIS, MAX_VIS+3).map((e,k) => {
                      const c = ChronoTokens.events[e.color];
                      return <span key={k} style={{
                        width: 4, height: 4, borderRadius: 999,
                        background: c.glow, boxShadow: theme.isDark ? `0 0 4px ${c.glow}` : 'none',
                      }}/>;
                    })}
                  </span>
                  +{hidden} more
                </button>
              )}
              {isPopOpen && (
                <div onClick={(ev)=>ev.stopPropagation()} style={{
                  position: 'absolute', zIndex: 40,
                  top: '100%', marginTop: 4,
                  [popRight ? 'right' : 'left']: 0,
                  width: 220, padding: 10, borderRadius: 12,
                  background: theme.isDark ? 'rgba(20,22,32,0.96)' : 'rgba(255,255,255,0.98)',
                  border: `0.5px solid ${theme.hairline}`,
                  boxShadow: '0 16px 40px rgba(0,0,0,0.45)',
                  backdropFilter: 'blur(40px)',
                  animation: `chrono-scale-in 180ms ${Spring.std}`,
                  display: 'flex', flexDirection: 'column', gap: 4,
                  transformOrigin: popRight ? 'top right' : 'top left',
                }}>
                  <div style={{
                    display: 'flex', alignItems: 'baseline', gap: 6,
                    padding: '2px 4px 6px',
                    borderBottom: `0.5px solid ${theme.hairline}`, marginBottom: 4,
                  }}>
                    <span style={{
                      fontFamily: ChronoTokens.font.display, fontSize: 16, fontWeight: 700,
                      color: theme.text, letterSpacing: '-0.02em',
                    }}>{d.getDate()}</span>
                    <span style={{
                      fontFamily: ChronoTokens.font.mono, fontSize: 10, fontWeight: 600,
                      color: theme.textDim, letterSpacing: '0.06em', textTransform: 'uppercase',
                    }}>{DAY_FULL[d.getDay()]}</span>
                    <span style={{ flex: 1 }}/>
                    <span style={{
                      fontFamily: ChronoTokens.font.mono, fontSize: 10, color: theme.textFaint,
                    }}>{evs.length} events</span>
                  </div>
                  {evs.map((e, j) => {
                    const c = ChronoTokens.events[e.color];
                    return (
                      <button key={j} className="chrono-tap"
                        onClick={()=>{closeAll(); onSelect(e);}}
                        style={{
                          appearance: 'none', cursor: 'pointer', textAlign: 'left',
                          border: 'none', borderLeft: `2.5px solid ${c.glow}`,
                          background: theme.isDark ? `${c.base}14` : `${c.base}0A`,
                          padding: '5px 8px', borderRadius: 5,
                          display: 'flex', alignItems: 'baseline', gap: 8,
                          color: theme.text,
                          fontFamily: ChronoTokens.font.ui, fontSize: 12, fontWeight: 500,
                        }}>
                        <span style={{
                          fontFamily: ChronoTokens.font.mono, fontSize: 10,
                          color: theme.textDim, minWidth: 38, fontWeight: 600,
                        }}>{fmtTime(e.start).replace(' ','')}</span>
                        <span style={{ flex: 1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{e.title}</span>
                      </button>
                    );
                  })}
                </div>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ── Focus full screen ────────────────────────────────────────────────────
function DesktopFocus({ theme }) {
  useEvents();
  const [selectedEvent, setSelectedEvent] = React.useState(null);
  const [pickerOpen, setPickerOpen] = React.useState(false);

  // Session length: event duration if picked, otherwise 25 min.
  const TOTAL = selectedEvent
    ? Math.max(60, (tm(selectedEvent.end) - tm(selectedEvent.start)) * 60)
    : 25 * 60;

  const [el, setEl] = React.useState(0);
  const [running, setRunning] = React.useState(false);

  // Reset timer when the chosen event (and therefore TOTAL) changes.
  React.useEffect(() => { setEl(0); setRunning(false); }, [selectedEvent]);

  React.useEffect(() => {
    if (!running) return;
    const id = setInterval(()=>setEl(e=>Math.min(TOTAL, e+1)), 1000);
    return () => clearInterval(id);
  }, [running, TOTAL]);

  const remaining = TOTAL - el;
  const mm = Math.floor(remaining/60);
  const ss = remaining % 60;
  const progress = el / TOTAL;
  const huePulse = Math.floor(progress * 60);

  // Upcoming events from CHRONO_TODAY → 30 days out.
  const upcoming = [];
  for (let off = 0; off <= 30; off++) {
    const evs = eventsForOffset(off);
    evs.forEach(ev => upcoming.push({ ev, off }));
  }
  upcoming.sort((a, b) => (a.off - b.off) || tm(a.ev.start) - tm(b.ev.start));

  const totalMinLabel = Math.round(TOTAL / 60);
  const sel = selectedEvent;
  const selColor = sel ? ChronoTokens.events[sel.color] : null;

  return (
    <div style={{ position: 'absolute', inset: 0, overflow: 'hidden', zIndex: 1 }}>
      <div style={{
        position: 'absolute', top: '15%', left: '20%', width: 500, height: 500,
        borderRadius: '50%',
        background: `radial-gradient(circle, oklch(60% 0.2 ${270+huePulse}) 0%, transparent 65%)`,
        filter: 'blur(100px)', animation: 'chrono-orb-1 14s ease-in-out infinite',
        opacity: theme.isDark ? 0.7 : 0.4,
      }}/>
      <div style={{
        position: 'absolute', bottom: '10%', right: '20%', width: 420, height: 420,
        borderRadius: '50%',
        background: `radial-gradient(circle, oklch(55% 0.22 ${320-huePulse}) 0%, transparent 65%)`,
        filter: 'blur(100px)', animation: 'chrono-orb-2 18s ease-in-out infinite',
        opacity: theme.isDark ? 0.6 : 0.3,
      }}/>
      <div style={{ position: 'relative', zIndex: 2, height: '100%', display: 'flex', flexDirection: 'column', padding: 32 }}>
        <div>
          <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 11, fontWeight: 600, color: theme.textFaint, letterSpacing: '0.12em', textTransform: 'uppercase' }}>
            Focus · {running ? 'In session' : (sel ? 'Ready' : 'Pick an event')}
          </div>
          <div style={{
            fontFamily: ChronoTokens.font.display, fontSize: 28, fontWeight: 700,
            letterSpacing: '-0.025em', display: 'flex', alignItems: 'center', gap: 10,
          }}>
            {sel && (
              <div style={{
                width: 4, height: 26, borderRadius: 2,
                background: `linear-gradient(180deg, ${selColor.base}, ${selColor.glow})`,
                boxShadow: theme.isDark ? `0 0 8px ${selColor.glow}88` : 'none',
              }}/>
            )}
            <span>{sel ? sel.title : 'No event selected'}</span>
          </div>

          {/* Picker trigger + dropdown */}
          <div style={{ position: 'relative', marginTop: 12, display: 'inline-block' }}>
            <button className="chrono-tap" onClick={()=>setPickerOpen(o=>!o)} style={{
              appearance: 'none', cursor: 'pointer',
              display: 'inline-flex', alignItems: 'center', gap: 8,
              padding: '7px 12px', borderRadius: 999,
              background: theme.isDark ? 'rgba(255,255,255,0.05)' : 'rgba(10,12,20,0.05)',
              border: `0.5px solid ${theme.hairline}`,
              color: theme.text,
              fontFamily: ChronoTokens.font.ui, fontSize: 12.5, fontWeight: 600,
            }}>
              <Icon.cal s={12}/>
              {sel ? 'Change event' : 'Choose event'}
              {sel && (
                <span onClick={(e)=>{ e.stopPropagation(); setSelectedEvent(null); setPickerOpen(false); }}
                  style={{
                    marginLeft: 4, padding: '2px 6px', borderRadius: 999,
                    background: theme.isDark ? 'rgba(255,255,255,0.08)' : 'rgba(10,12,20,0.08)',
                    color: theme.textDim, fontSize: 10, fontWeight: 700,
                  }}>Clear</span>
              )}
            </button>

            {pickerOpen && (
              <div style={{
                position: 'absolute', top: 'calc(100% + 8px)', left: 0,
                width: 340, maxHeight: 360, overflow: 'auto', zIndex: 10,
                background: theme.isDark ? 'rgba(20,22,32,0.96)' : 'rgba(255,255,255,0.98)',
                border: `0.5px solid ${theme.hairline}`,
                borderRadius: 12,
                boxShadow: theme.isDark
                  ? `0 20px 50px rgba(0,0,0,0.55), 0 0 0 1px rgba(255,255,255,0.04)`
                  : `0 20px 50px rgba(10,12,20,0.18)`,
                backdropFilter: 'blur(30px)',
                padding: 6,
              }} className="chrono-scrollbar">
                {upcoming.length === 0 ? (
                  <div style={{
                    padding: '20px 14px', textAlign: 'center',
                    fontFamily: ChronoTokens.font.ui, fontSize: 13, color: theme.textDim,
                  }}>
                    No upcoming events. Create one in the calendar first.
                  </div>
                ) : upcoming.map(({ ev, off }, i) => {
                  const c = ChronoTokens.events[ev.color];
                  const d = dateAtOffset(off);
                  const dayLabel = off === 0 ? 'Today'
                                 : off === 1 ? 'Tomorrow'
                                 : `${DAY_NAMES[d.getDay()]} · ${MONTH_NAMES[d.getMonth()].slice(0,3)} ${d.getDate()}`;
                  const dur = tm(ev.end) - tm(ev.start);
                  return (
                    <button key={`${off}-${i}`} className="chrono-tap"
                      onClick={()=>{ setSelectedEvent(ev); setPickerOpen(false); }}
                      style={{
                        appearance: 'none', cursor: 'pointer', border: 'none',
                        display: 'flex', alignItems: 'center', gap: 10,
                        width: '100%', textAlign: 'left',
                        padding: '10px 12px', borderRadius: 8,
                        background: 'transparent', color: theme.text,
                      }}
                      onMouseEnter={e=>e.currentTarget.style.background = theme.isDark ? 'rgba(255,255,255,0.04)' : 'rgba(10,12,20,0.04)'}
                      onMouseLeave={e=>e.currentTarget.style.background = 'transparent'}
                    >
                      <div style={{
                        width: 3, alignSelf: 'stretch', borderRadius: 2,
                        background: `linear-gradient(180deg, ${c.base}, ${c.glow})`,
                      }}/>
                      <div style={{ flex: 1, minWidth: 0 }}>
                        <div style={{
                          fontFamily: ChronoTokens.font.ui, fontSize: 13.5, fontWeight: 600,
                          overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                        }}>{ev.title}</div>
                        <div style={{
                          fontFamily: ChronoTokens.font.mono, fontSize: 10.5, color: theme.textDim,
                          marginTop: 2,
                        }}>
                          {dayLabel} · {fmtTime(ev.start)} → {fmtTime(ev.end)}
                        </div>
                      </div>
                      <div style={{
                        fontFamily: ChronoTokens.font.mono, fontSize: 10.5, fontWeight: 700,
                        color: theme.accentColor, letterSpacing: '0.06em',
                      }}>{dur}m</div>
                    </button>
                  );
                })}
              </div>
            )}
          </div>
        </div>

        <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          <div style={{ textAlign: 'center' }}>
            <div style={{
              fontFamily: ChronoTokens.font.display, fontSize: 180, fontWeight: 800,
              color: theme.text, letterSpacing: '-0.05em', lineHeight: 1,
              fontVariantNumeric: 'tabular-nums',
              textShadow: theme.isDark ? `0 0 60px ${theme.accentColor}66` : 'none',
            }}>
              {mm.toString().padStart(2,'0')}<span style={{ opacity: 0.3 }}>:</span>{ss.toString().padStart(2,'0')}
            </div>
            <div style={{ display: 'inline-flex', gap: 16, marginTop: 32 }}>
              <DeskBtn theme={theme} onClick={()=>{ setEl(0); setRunning(false); }}><Icon.stop s={14}/></DeskBtn>
              <button className="chrono-tap" onClick={()=>setRunning(r=>!r)} style={{
                appearance: 'none', cursor: 'pointer', border: 'none',
                width: 64, height: 64, borderRadius: 999,
                background: `linear-gradient(180deg, ${theme.accentColor}, ${theme.accentColor}CC)`,
                color: '#fff',
                display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                boxShadow: theme.accentGlow,
              }}>{running ? <Icon.pause s={24}/> : <Icon.play s={24}/>}</button>
              <DeskBtn theme={theme} onClick={()=>setEl(TOTAL)}><Icon.fwd s={14}/></DeskBtn>
            </div>
            <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 12, color: theme.textDim, marginTop: 24, letterSpacing: '0.16em', textTransform: 'uppercase' }}>
              {sel
                ? `Press play to start a ${totalMinLabel}-minute session`
                : `Press play to start a 25-minute session`}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

// ── Settings full ────────────────────────────────────────────────────────
function DesktopSettings({ theme }) {
  const settings = useSettings();
  const integrations = useIntegrations();
  const [connectingCanvas, setConnectingCanvas] = React.useState(false);
  const set = (k, v) => setSettings({ [k]: v });

  const durationOpts = ['15','30','45','60','90','120'].map(n => ({ value:n, label:`${n} min` }));
  const leadOpts = ['0','5','10','15','30','60'].map(n => ({ value:n, label: n === '0' ? 'At start' : `${n} min` }));
  const bufferOpts = ['0','5','10','15','30'].map(n => ({ value:n, label: n === '0' ? 'None' : `${n} min` }));

  return (
    <div style={{ position: 'absolute', inset: 0, overflow: 'auto', zIndex: 1 }} className="chrono-scrollbar">
      <DesktopHeader theme={theme} eyebrow="Configure" title="Settings"/>
      <div style={{ padding: '6px 24px 40px', maxWidth: 640, display: 'flex', flexDirection: 'column', gap: 22 }}>

        <DeskGroup theme={theme} title="Time & Calendar">
          <DeskRow theme={theme} k="Week starts on" desc="The first day of every week and month grid."
            help="Sets which weekday sits in the leftmost column of the Week view and the first column of the Month grid. Choose Monday if you think of your week as starting on a workday, or Sunday for the traditional US calendar layout."
            editor={{ kind: 'select', value: String(settings.weekStartsOn),
              options: [{value:'0',label:'Sunday'},{value:'1',label:'Monday'}],
              onSave: v => set('weekStartsOn', parseInt(v, 10)) }}/>
          <DeskRow theme={theme} k="Time format" desc="How times appear across Chrono."
            help="Switches every displayed time between 12-hour (2:30 PM) and 24-hour (14:30) — the calendar axis, event times, and the assistant's replies all follow this."
            editor={{ kind: 'select', value: settings.timeFormat,
              options: [{value:'12',label:'12-hour'},{value:'24',label:'24-hour'}],
              onSave: v => set('timeFormat', v) }}/>
          <DeskRow theme={theme} k="Default event length" desc="Pre-filled duration when you create an event."
            help="When you make a new event without specifying an end time, Chrono pre-fills this length. You can always drag the event's edge to resize it afterward."
            editor={{ kind: 'select', value: String(settings.defaultDuration),
              options: durationOpts,
              onSave: v => set('defaultDuration', parseInt(v, 10)) }}/>
          <DeskRow theme={theme} k="Show holidays" desc="Mark US public holidays on the calendar." last
            help="Overlays US public holidays (e.g. Juneteenth, Thanksgiving) as labels on the Week and Month views. Turn off for a cleaner grid; it doesn't create events or block scheduling."
            editor={{ kind: 'toggle', value: settings.showHolidays,
              onSave: v => set('showHolidays', v) }}/>
        </DeskGroup>

        <DeskGroup theme={theme} title="Assistant">
          <DeskRow theme={theme} k="When prompts are vague"
            desc="Choose whether the assistant asks for details first or just acts on smart defaults."
            help="“Use smart defaults” makes the assistant act immediately on incomplete requests, picking sensible times and durations you can adjust later. “Ask me details” has it pause and ask one focused question first when a request is genuinely ambiguous — better when precision matters more than speed."
            editor={{ kind: 'select', value: settings.aiFollowups,
              options: [
                { value: 'auto', label: 'Use smart defaults' },
                { value: 'ask',  label: 'Ask me details' },
              ],
              onSave: v => set('aiFollowups', v) }}/>
          <DeskRow theme={theme} k="Planning buffer" desc="Minimum gap the assistant leaves around events it schedules."
            help="Empty breathing room the assistant tries to keep before and after the blocks it schedules, so back-to-back events don't pile up. It applies when planning days, weeks, and trips — set to None to pack things tightly."
            editor={{ kind: 'select', value: String(settings.aiBufferMin),
              options: bufferOpts,
              onSave: v => set('aiBufferMin', parseInt(v, 10)) }}/>
          <DeskRow theme={theme} k="Show reasoning" desc="Display the short rationale behind each suggestion." last
            help="When on, the assistant adds a brief line explaining why it chose a particular time or plan. Turn off if you just want the result without the explanation."
            editor={{ kind: 'toggle', value: settings.aiShowReasoning,
              onSave: v => set('aiShowReasoning', v) }}/>
        </DeskGroup>

        <DeskGroup theme={theme} title="Notifications">
          <DeskRow theme={theme} k="Reminder lead time" desc="How early to nudge you before an event starts."
            help="Sets the default head-start for reminders — e.g. 10 minutes means you're nudged 10 minutes before an event begins. “At start” fires exactly when the event begins."
            editor={{ kind: 'select', value: String(settings.notifLead),
              options: leadOpts,
              onSave: v => set('notifLead', parseInt(v, 10)) }}/>
          <DeskRow theme={theme} k="Sounds" desc="Play a chime with reminders." last
            help="Plays a short chime alongside reminder pop-ups. Turn off for silent, visual-only notifications."
            editor={{ kind: 'toggle', value: settings.sounds,
              onSave: v => set('sounds', v) }}/>
        </DeskGroup>

        <DeskGroup theme={theme} title="Integrations">
          <DeskRow theme={theme} k="Canvas LMS"
            desc={integrations.canvas
              ? `Connected · ${integrations.canvasSchool} · ${integrations.canvasCourses.length} courses`
              : 'Import courses, due dates and exams onto your calendar.'}
            badge={integrations.canvas ? 'Connected' : 'Connect'}
            badgeTone={integrations.canvas ? 'on' : 'action'}
            onClick={integrations.canvas
              ? () => { if (confirm('Disconnect Canvas? Imported assignments will be removed from your calendar.')) { clearCanvasEvents(); setIntegrations({ canvas: false, canvasSchool: '', canvasCourses: [] }); } }
              : () => setConnectingCanvas(true)}/>
          <DeskRow theme={theme} k="Google Calendar" desc="Two-way sync with your Google events." badge="Coming soon" muted/>
          <DeskRow theme={theme} k="Outlook 365" desc="Sync events from your Microsoft account." badge="Coming soon" muted/>
          <DeskRow theme={theme} k="Apple iCloud" desc="Mirror your iCloud calendars." badge="Coming soon" muted last/>
        </DeskGroup>

        <DeskGroup theme={theme} title="Privacy"
          desc="Your prompts are sent to the AI provider only to generate a response — nothing more.">
          <DeskRow theme={theme} k="Prompts aren't stored"
            desc="Requests are sent with no-store, so the provider doesn't retain them after replying."
            badge="On" badgeTone="on"/>
          <DeskRow theme={theme} k="Never used for training"
            desc="Data sent through the API is not used to train the provider's models."
            badge="On" badgeTone="on"/>
          <DeskRow theme={theme} k="No prompt logging"
            desc="Chrono's own server never logs your prompt or event content."
            badge="On" badgeTone="on"/>
          <DeskRow theme={theme} k="No tracking identifiers"
            desc="No account ID or analytics tag is attached to AI requests."
            badge="On" badgeTone="on" last/>
        </DeskGroup>

        <DeskGroup theme={theme} title="Appearance"
          desc="Accent, background and futurism live in the Tweaks panel (bottom-right).">
          <DeskRow theme={theme} k="Theme & background" desc="Open the Tweaks panel to customize the look."
            badge="Tweaks panel" muted last/>
        </DeskGroup>

        <DeskGroup theme={theme} title="Reset">
          <DeskRow theme={theme} k="Restore default settings" desc="Return every setting above to its default."
            badge="Reset" badgeTone="danger"
            onClick={()=>{ if (confirm('Reset all settings to defaults?')) resetSettings(); }}/>
          <DeskRow theme={theme} k="Re-run onboarding" desc="Replay the welcome survey and tutorial."
            badge="Start" badgeTone="action" last
            onClick={()=>{ if (confirm('Re-run the onboarding survey and tutorial?')) resetOnboarding(); }}/>
        </DeskGroup>
      </div>
      {connectingCanvas && <CanvasConnectModal theme={theme} onClose={()=>setConnectingCanvas(false)}/>}
    </div>
  );
}

// Settings group: a section title, optional one-line description, and a rounded
// card holding the rows. Spotify-style — everything visible at once, no tabs.
function DeskGroup({ theme, title, desc, children }) {
  return (
    <div>
      <div style={{
        fontFamily: ChronoTokens.font.display, fontSize: 15, fontWeight: 700,
        color: theme.text, letterSpacing: '-0.01em', padding: '0 2px 2px',
      }}>{title}</div>
      {desc && (
        <div style={{
          fontFamily: ChronoTokens.font.ui, fontSize: 12, color: theme.textDim,
          lineHeight: 1.4, padding: '0 2px 10px', maxWidth: 560,
        }}>{desc}</div>
      )}
      <div style={{
        marginTop: desc ? 0 : 8,
        background: theme.isDark ? 'rgba(255,255,255,0.025)' : '#fff',
        border: `0.5px solid ${theme.hairline}`,
        // overflow visible so a row's hover tooltip can extend past the card
        // edge instead of being clipped. Rows stay inside the rounded border;
        // only the (opaque, high z-index) tooltip escapes.
        borderRadius: 14, overflow: 'visible',
      }}>{children}</div>
    </div>
  );
}

// A single settings row: label + description on the left, an inline control on
// the right (a switch, a dropdown, or a tappable badge for actions / status).
function DeskRow({ theme, k, desc, help, editor, onClick, muted, badge, badgeTone, last }) {
  const isToggle = editor && editor.kind === 'toggle';
  // A toggle is operated by its own switch; the row itself isn't clickable.
  const rowClickable = !muted && !!onClick && !isToggle;

  // Hover-to-learn: after the pointer rests on a setting for 5 seconds, reveal
  // a fuller description of what it does. Settings-only (DeskRow is used only
  // here). Clears immediately on mouse-out so it never lingers.
  const tipText = help || desc;
  const rowRef = React.useRef(null);
  const [showTip, setShowTip] = React.useState(false);
  const [placeBelow, setPlaceBelow] = React.useState(false);
  const tipTimer = React.useRef(null);
  const HOVER_DELAY_MS = 450; // snappy: brief intentional pause, then reveal
  const startTip = () => {
    if (!tipText) return;
    clearTimeout(tipTimer.current);
    tipTimer.current = setTimeout(() => {
      // Flip the tooltip below the row when there isn't enough room above it
      // within the scrolling settings panel, so it never gets cut off.
      const el = rowRef.current;
      if (el) {
        const scroller = el.closest('.chrono-scrollbar');
        const top = el.getBoundingClientRect().top;
        const bound = scroller ? scroller.getBoundingClientRect().top : 0;
        setPlaceBelow((top - bound) < 140);
      }
      setShowTip(true);
    }, HOVER_DELAY_MS);
  };
  const endTip = () => { clearTimeout(tipTimer.current); setShowTip(false); };
  React.useEffect(() => () => clearTimeout(tipTimer.current), []);

  return (
    <div
      ref={rowRef}
      onClick={rowClickable ? onClick : undefined}
      style={{
        position: 'relative',
        display: 'flex', alignItems: 'center', gap: 16,
        padding: '13px 16px',
        borderBottom: last ? 'none' : `0.5px solid ${theme.hairline}`,
        opacity: muted ? 0.65 : 1,
        cursor: rowClickable ? 'pointer' : 'default',
        transition: 'background 160ms ease',
      }}
      onMouseEnter={e => { if (rowClickable) e.currentTarget.style.background = theme.isDark ? 'rgba(255,255,255,0.03)' : 'rgba(10,12,20,0.02)'; startTip(); }}
      onMouseLeave={e => { e.currentTarget.style.background = 'transparent'; endTip(); }}
    >
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontFamily: ChronoTokens.font.ui, fontSize: 13.5, fontWeight: 600, color: theme.text }}>{k}</div>
        {desc && (
          <div style={{ fontFamily: ChronoTokens.font.ui, fontSize: 11.5, color: theme.textDim, marginTop: 2, lineHeight: 1.35 }}>{desc}</div>
        )}
      </div>
      <div style={{ flexShrink: 0, display: 'flex', alignItems: 'center' }}>
        {editor
          ? <DeskControl theme={theme} editor={editor}/>
          : <DeskBadge theme={theme} label={badge} tone={badgeTone} muted={muted}/>}
      </div>

      {/* Hover tooltip — opaque card with a gap from the row, flipped above or
          below depending on room, so it never bleeds into or gets clipped by
          the neighbouring settings or the panel edge. */}
      {showTip && tipText && (
        <div role="tooltip" style={{
          position: 'absolute', left: 12, right: 12, zIndex: 90,
          ...(placeBelow ? { top: 'calc(100% + 8px)' } : { bottom: 'calc(100% + 8px)' }),
          padding: '9px 12px', borderRadius: 10,
          // Solid (no alpha) so underlying rows can't show through it.
          background: theme.isDark ? '#171a24' : '#ffffff',
          border: `1px solid ${theme.accentColor}66`,
          boxShadow: theme.isDark
            ? '0 14px 36px rgba(0,0,0,0.7), 0 0 0 1px rgba(0,0,0,0.4)'
            : '0 14px 36px rgba(10,12,20,0.22)',
          fontFamily: ChronoTokens.font.ui, fontSize: 12, lineHeight: 1.45, color: theme.text,
          pointerEvents: 'none',
          animation: 'chrono-backdrop-in 120ms ease both',
        }}>
          <div style={{
            fontFamily: ChronoTokens.font.mono, fontSize: 9, fontWeight: 700, letterSpacing: '0.12em',
            textTransform: 'uppercase', color: theme.accentColor, marginBottom: 4,
          }}>{k}</div>
          {tipText}
        </div>
      )}
    </div>
  );
}

// Inline status / action chip used on the right of a row.
function DeskBadge({ theme, label, tone, muted }) {
  if (!label) return null;
  const palette = {
    on:     { fg: '#52d094', bd: '#52d09455', bg: 'rgba(82,208,148,0.12)' },
    action: { fg: theme.accentColor, bd: `${theme.accentColor}55`, bg: `${theme.accentColor}14` },
    danger: { fg: ChronoTokens.events.red.base, bd: `${ChronoTokens.events.red.base}55`, bg: `${ChronoTokens.events.red.base}14` },
    muted:  { fg: theme.textFaint, bd: theme.hairline, bg: 'transparent' },
  };
  const p = muted ? palette.muted : (palette[tone] || palette.action);
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', gap: 6,
      padding: '4px 11px', borderRadius: 999,
      border: `0.5px solid ${p.bd}`, background: p.bg, color: p.fg,
      fontFamily: ChronoTokens.font.ui, fontSize: 11.5, fontWeight: 700, letterSpacing: '0.01em',
      whiteSpace: 'nowrap',
    }}>{label}</span>
  );
}

// Inline control — a switch for toggles, a native dropdown for selects. Both
// commit immediately (no expand-then-confirm step), the way Spotify does it.
function DeskControl({ theme, editor }) {
  if (editor.kind === 'toggle') {
    const on = !!editor.value;
    return (
      <button onClick={()=>editor.onSave(!on)} aria-pressed={on} style={{
        appearance: 'none', cursor: 'pointer', border: 'none',
        width: 44, height: 26, borderRadius: 999,
        background: on ? theme.accentColor : (theme.isDark ? 'rgba(255,255,255,0.1)' : 'rgba(10,12,20,0.12)'),
        padding: 2, position: 'relative',
        boxShadow: on ? theme.accentGlow : 'none',
        transition: `background 220ms ${Spring.snap}`,
      }}>
        <div style={{
          width: 22, height: 22, borderRadius: 999, background: '#fff',
          transform: on ? 'translateX(18px)' : 'translateX(0)',
          transition: `transform 220ms ${Spring.std}`,
          boxShadow: '0 1px 3px rgba(0,0,0,0.2)',
        }}/>
      </button>
    );
  }
  if (editor.kind === 'select') {
    return (
      <div style={{ position: 'relative', display: 'inline-flex', alignItems: 'center' }}>
        <select
          value={String(editor.value)}
          onChange={e => editor.onSave(e.target.value)}
          style={{
            appearance: 'none', WebkitAppearance: 'none', MozAppearance: 'none',
            cursor: 'pointer', outline: 'none',
            padding: '7px 30px 7px 13px', borderRadius: 999,
            border: `0.5px solid ${theme.hairline}`,
            background: theme.isDark ? 'rgba(255,255,255,0.05)' : 'rgba(10,12,20,0.04)',
            color: theme.text,
            fontFamily: ChronoTokens.font.ui, fontSize: 12.5, fontWeight: 600,
            colorScheme: theme.isDark ? 'dark' : 'light',
          }}>
          {editor.options.map(opt => (
            <option key={opt.value} value={opt.value}>{opt.label}</option>
          ))}
        </select>
        <span style={{
          position: 'absolute', right: 11, top: '50%', transform: 'translateY(-50%) rotate(90deg)',
          color: theme.textFaint, pointerEvents: 'none', display: 'inline-flex',
        }}><Icon.fwd s={10}/></span>
      </div>
    );
  }
  return null;
}

// ── AI Panel (right side) ────────────────────────────────────────────────
function DesktopAIPanel({ theme, personality, onClose }) {
  return (
    <div data-tutorial-id="ai-area" style={{
      position: 'relative',
      borderLeft: `0.5px solid ${theme.hairline}`,
      background: theme.isDark ? 'rgba(10,12,20,0.5)' : 'rgba(248,248,252,0.6)',
      backdropFilter: 'blur(40px)',
      display: 'flex', flexDirection: 'column',
      // minHeight:0 + overflow:hidden let the inner chat body scroll instead of
      // pushing the panel taller than its grid cell when the chat chain grows.
      minHeight: 0,
      overflow: 'hidden',
      zIndex: 3,
    }}>
      <div style={{
        flexShrink: 0,
        padding: '14px 16px', display: 'flex', alignItems: 'center', gap: 10,
        borderBottom: `0.5px solid ${theme.hairline}`,
      }}>
        <div style={{
          width: 28, height: 28, borderRadius: 8,
          background: `radial-gradient(circle at 30% 30%, ${theme.accentColor}, ${theme.accentColor}66)`,
          boxShadow: theme.accentGlow,
          display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: '#fff',
        }}><Icon.sparkle s={12}/></div>
        <div style={{ flex: 1 }}>
          <div style={{ fontFamily: ChronoTokens.font.display, fontSize: 14, fontWeight: 700, letterSpacing: '-0.01em' }}>Assistant</div>
          <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 9.5, color: theme.textFaint, letterSpacing: '0.1em', textTransform: 'uppercase' }}>{personality} · live</div>
        </div>
        <button className="chrono-tap" onClick={onClose} style={{
          appearance: 'none', cursor: 'pointer', border: 'none', background: 'transparent',
          color: theme.textDim, padding: 4,
        }}><Icon.close s={14}/></button>
      </div>
      <div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
        <DesktopAIBody theme={theme} personality={personality}/>
      </div>
    </div>
  );
}

function DesktopAIFull({ theme, personality }) {
  return (
    <div style={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', zIndex: 1 }}>
      <DesktopHeader theme={theme} eyebrow={`${personality} mode · live`} title="Assistant"/>
      <div style={{ flex: 1, padding: '0 24px', maxWidth: 720, width: '100%', margin: '0 auto', minHeight: 0 }}>
        <DesktopAIBody theme={theme} personality={personality}/>
      </div>
    </div>
  );
}

// ── Chat sub-components ──────────────────────────────────────────────
function ChatBubble({ m, theme, onResolveSuggestion }) {
  const isUser = m.role === 'user';
  const text = m.text || m.content || '';
  const offer = m.suggestion;
  const ec = offer && ChronoTokens.events[offer.color] ? ChronoTokens.events[offer.color] : ChronoTokens.events.grey;
  return (
    <div style={{
      alignSelf: isUser ? 'flex-end' : 'flex-start',
      maxWidth: '85%',
      padding: '8px 12px', borderRadius: 12,
      background: isUser
        ? `linear-gradient(180deg, ${theme.accentColor}33, ${theme.accentColor}1A)`
        : (theme.isDark ? 'rgba(255,255,255,0.04)' : 'rgba(10,12,20,0.04)'),
      border: `0.5px solid ${isUser ? theme.accentColor + '55' : theme.hairline}`,
      color: theme.text,
      fontFamily: ChronoTokens.font.ui, fontSize: 13, lineHeight: 1.5,
      whiteSpace: 'pre-wrap', wordBreak: 'break-word',
    }}>
      {text || (m.role === 'assistant' && m.error
        ? <span style={{ color: ChronoTokens.events.red.base }}>{m.error}</span>
        : null)}

      {/* Category offer — Yes/No while unresolved, status line once answered. */}
      {offer && !m.suggestionResolved && (
        <div style={{ marginTop: 10, display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
          <button className="chrono-tap" onClick={()=>onResolveSuggestion && onResolveSuggestion(true)} style={{
            appearance: 'none', cursor: 'pointer', border: 'none',
            display: 'inline-flex', alignItems: 'center', gap: 6,
            padding: '5px 12px', borderRadius: 999,
            background: `linear-gradient(135deg, ${ec.base}, ${ec.glow})`,
            color: '#fff', fontFamily: ChronoTokens.font.ui, fontSize: 12, fontWeight: 600,
            boxShadow: `0 0 12px ${ec.glow}55`,
          }}>
            <span style={{ width: 7, height: 7, borderRadius: 2, background: 'rgba(255,255,255,0.9)' }}/>
            Create “{offer.name}”
          </button>
          <button className="chrono-tap" onClick={()=>onResolveSuggestion && onResolveSuggestion(false)} style={{
            appearance: 'none', cursor: 'pointer',
            padding: '5px 12px', borderRadius: 999,
            border: `0.5px solid ${theme.hairline}`, background: 'transparent',
            color: theme.textDim, fontFamily: ChronoTokens.font.ui, fontSize: 12, fontWeight: 600,
          }}>No thanks</button>
        </div>
      )}
      {offer && m.suggestionResolved && (
        <div style={{
          marginTop: 8, fontFamily: ChronoTokens.font.mono, fontSize: 9.5,
          color: theme.textFaint, letterSpacing: '0.06em',
        }}>
          {m.suggestionResolved === 'accepted'
            ? `✓ Created “${offer.name}” and tagged the event${(m.suggestRefs || []).length === 1 ? '' : 's'}.`
            : '· Left uncategorized.'}
        </div>
      )}

      {m.meta && (
        <div style={{
          marginTop: 6,
          fontFamily: ChronoTokens.font.mono, fontSize: 9.5,
          color: theme.textFaint, letterSpacing: '0.08em',
        }}>{m.meta}</div>
      )}
    </div>
  );
}

function StreamingBubble({ theme, text }) {
  return (
    <div style={{
      alignSelf: 'flex-start', maxWidth: '85%',
      padding: '8px 12px', borderRadius: 12,
      background: theme.isDark ? 'rgba(255,255,255,0.04)' : 'rgba(10,12,20,0.04)',
      border: `0.5px solid ${theme.hairline}`,
      color: theme.text,
      fontFamily: ChronoTokens.font.ui, fontSize: 13, lineHeight: 1.5,
      whiteSpace: 'pre-wrap', wordBreak: 'break-word',
    }}>
      {text || ''}
      <span aria-hidden style={{
        display: 'inline-block', width: 2, height: '1em',
        background: theme.accentColor, marginLeft: 2,
        verticalAlign: 'text-bottom',
        animation: 'chrono-blink 1s steps(2) infinite',
      }}/>
    </div>
  );
}

// Map a day label or ISO date from the model into a day-offset from CHRONO_TODAY.
function offsetFromDayLabel(label) {
  if (!label) return 0;
  const s = String(label).trim();
  const lower = s.toLowerCase();
  if (lower === 'today')    return 0;
  if (lower === 'tomorrow') return 1;
  const DOW = ['sunday','monday','tuesday','wednesday','thursday','friday','saturday'];
  const idx = DOW.indexOf(lower);
  if (idx >= 0) {
    // Resolve weekday → nearest upcoming occurrence INCLUDING today. When
    // today is Monday and the model says "Monday", that means today, not
    // a week from now. (The model is instructed to use ISO dates for
    // unambiguous cases.)
    const today = CHRONO_TODAY.getDay();
    return ((idx - today) + 7) % 7;
  }
  // ISO YYYY-MM-DD
  if (/^\d{4}-\d{2}-\d{2}$/.test(s)) {
    const [y, mo, d] = s.split('-').map(n => parseInt(n, 10));
    const target = new Date(y, mo - 1, d); target.setHours(0,0,0,0);
    return Math.round((target - CHRONO_TODAY) / 86400000);
  }
  return 0;
}

// Flatten the runtime event store into a model-readable shape, with stable ids.
function snapshotEventsForAI() {
  const out = [];
  Object.keys(EVENTS_BY_OFFSET).forEach(k => {
    const off = parseInt(k, 10);
    const d = dateAtOffset(off);
    const ymd = `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
    EVENTS_BY_OFFSET[k].forEach((e, i) => {
      out.push({
        id: `ev-${off}-${i}`,
        date: ymd,
        start: e.start,
        end: e.end,
        title: e.title,
        color: e.color,
        category: e.category || null,
        _ref: e, // server never sees this; used locally for delete
      });
    });
  });
  return out;
}

function DesktopAIBody({ theme, personality }) {
  const [messages, setMessages] = React.useState([]);
  const [input, setInput] = React.useState('');
  const [streaming, setStreaming] = React.useState(false);
  const [streamText, setStreamText] = React.useState('');
  const scrollRef = React.useRef(null);
  const abortRef = React.useRef(null);

  const onboarding = useOnboarding();
  const settings = useSettings();

  React.useEffect(() => {
    if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
  }, [messages, streaming, streamText]);

  React.useEffect(() => () => { if (abortRef.current) abortRef.current.abort(); }, []);

  const send = async (text) => {
    const msg = (text || input).trim();
    if (!msg || streaming) return;
    setInput('');

    const newHistory = [...messages, { role: 'user', text: msg }];
    setMessages(newHistory);
    setStreaming(true);
    setStreamText('');

    // Bail out if the streamer isn't on the page (e.g. served without the
    // proxy running). Surface a clear message.
    if (typeof window.streamAssistant !== 'function') {
      setMessages(m => [...m, {
        role: 'assistant',
        error: 'AI client not loaded. Make sure lib/ai-client.jsx is included and the proxy is running on http://localhost:3001.',
      }]);
      setStreaming(false);
      return;
    }

    abortRef.current = new AbortController();
    const eventsSnapshot = snapshotEventsForAI();
    const eventsForServer = eventsSnapshot.map(({ _ref, ...rest }) => rest);

    const a = onboarding.answers || {};
    const userContext = {
      role: a.role,
      domains: a.domains,
      goals: a.goals,
      gymGoal: a.gymGoal,
      procrastination: a.procrastination,
      peak: a.peak,
      sessionLen: a.sessionLen,
      wake: a.wake,
      bed: a.bed,
      density: a.density,
      timeFormat: settings.timeFormat,
      bufferMin: settings.aiBufferMin,
      showReasoning: settings.aiShowReasoning,
      followups: settings.aiFollowups,
      categories: CATEGORIES.map(c => ({ id: c.id, name: c.name, color: c.color })),
    };

    let buffer = '';
    let metaInfo = null;

    try {
      await window.streamAssistant({
        messages: newHistory.map(m => ({ role: m.role, content: m.text || m.content || '' })),
        personality,
        events: eventsForServer,
        userContext,
        signal: abortRef.current.signal,
        onMeta: (m) => { metaInfo = m; },
        onDelta: (d) => { buffer += d.text || ''; setStreamText(buffer); },
        onDone: (final) => {
          const replyText = (final && final.text) || buffer || '';
          // Refs of events created THIS turn that landed uncategorized — if the
          // user accepts an AI-proposed category, we re-tag exactly these.
          const uncategorizedRefs = [];
          // Apply <event> / <remove> mutations to the local store. `events`
          // is always an array — empty when the model proposed nothing,
          // single-element for a one-event add, multi-element for week plans.
          if (final && Array.isArray(final.events)) {
            final.events.forEach(ev => {
              if (!ev || typeof ev !== 'object') return;
              const off = offsetFromDayLabel(ev.day || ev.date);
              // Resolve category and color together. Priority:
              //   1. Explicit category id that matches an existing category.
              //   2. Explicit color that matches an existing category's color.
              //   3. Otherwise leave UNCATEGORIZED → grey. We never invent a
              //      category here; the model asks first via <suggestcategory>.
              let category = null, color = 'grey';
              if (ev.category && CATEGORIES.some(c => c.id === ev.category)) {
                category = ev.category;
                color = ev.color && ev.color !== 'grey'
                  ? ev.color
                  : CATEGORIES.find(c => c.id === category).color;
              } else if (ev.color && ev.color !== 'grey') {
                // Color-only: adopt it ONLY if it maps to an existing category.
                // Otherwise the event is uncategorized and must render grey.
                const m = CATEGORIES.find(c => c.color === ev.color);
                if (m) { category = m.id; color = ev.color; }
                else   { category = null; color = 'grey'; }
              }
              const ref = addEventAtOffset(off, {
                title: ev.title || 'Untitled',
                start: ev.start || '09:00',
                end: ev.end || '10:00',
                color, category,
                note: ev.note || '',
              });
              if (!category) uncategorizedRefs.push(ref);
            });
          }
          if (final && Array.isArray(final.removeIds) && final.removeIds.length) {
            // Map the model's ids back to our local _ref using the snapshot.
            const byId = new Map(eventsSnapshot.map(e => [e.id, e._ref]));
            final.removeIds.forEach(id => {
              const ref = byId.get(id);
              if (ref) deleteEventByRef(ref);
            });
          }
          const meta = metaInfo
            ? `${metaInfo.provider || ''}${metaInfo.provider ? ':' : ''}${metaInfo.model || ''} · ${final?.latencyMs ?? '?'}ms`
            : null;
          // If the model proposed creating a category for these events, attach
          // the offer + the just-created refs so the bubble can render Yes/No.
          const suggestion = final && final.suggestion && final.suggestion.name
            ? { name: String(final.suggestion.name).slice(0, 40), color: final.suggestion.color || 'grey' }
            : null;
          const assistantMsg = { role: 'assistant', text: replyText, meta };
          if (suggestion && uncategorizedRefs.length) {
            assistantMsg.suggestion = suggestion;
            assistantMsg.suggestRefs = uncategorizedRefs;
          }
          setMessages(m => [...m, assistantMsg]);
          setStreaming(false);
          setStreamText('');
        },
        onError: (e) => {
          setMessages(m => [...m, {
            role: 'assistant',
            error: e?.message || 'Assistant request failed.',
          }]);
          setStreaming(false);
          setStreamText('');
        },
      });
    } catch (err) {
      setMessages(m => [...m, { role: 'assistant', error: err?.message || 'Unexpected error.' }]);
      setStreaming(false);
      setStreamText('');
    }
  };

  // Resolve an AI category offer attached to message #i. Accept → create the
  // category and re-tag the events it was offered for. Decline → leave them
  // grey/uncategorized. Either way the offer is marked resolved so the
  // Yes/No chips collapse to a status line.
  const resolveSuggestion = (i, accept) => {
    setMessages(prev => prev.map((m, idx) => {
      if (idx !== i || !m.suggestion || m.suggestionResolved) return m;
      if (accept) {
        const cat = addCategory({ name: m.suggestion.name, color: m.suggestion.color });
        (m.suggestRefs || []).forEach(ref => setEventCategory(ref, cat.id, cat.color));
        return { ...m, suggestionResolved: 'accepted', suggestion: { ...m.suggestion, name: cat.name } };
      }
      return { ...m, suggestionResolved: 'declined' };
    }));
  };

  // Starter prompts tailored to the user's mix.
  const starters = (() => {
    const a = onboarding.answers || {};
    const domains = Array.isArray(a.domains) ? a.domains : [];
    const out = [];
    if (domains.includes('school'))   out.push('Plan study blocks this week');
    if (domains.includes('sidebuild'))out.push('Block 2h deep work tomorrow');
    if (domains.includes('gym'))      out.push('Add my gym sessions this week');
    if (domains.includes('pm'))       out.push('Schedule sprint planning Monday');
    if (Array.isArray(a.procrastination) && a.procrastination.length) {
      out.push("I'm stuck — give me a 15-min starter");
    }
    while (out.length < 3) out.push(['Plan my week','Move my 1:1 to Friday','What\'s on tomorrow?'][out.length] || 'Plan my week');
    return out.slice(0, 3);
  })();

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
      <div ref={scrollRef} className="chrono-scrollbar" style={{ flex: 1, overflow: 'auto', padding: '14px 16px', display: 'flex', flexDirection: 'column', gap: 8 }}>
        {messages.length === 0 && !streaming && <AIEmptyState theme={theme}/>}
        {messages.map((m, i) => <ChatBubble key={i} m={m} theme={theme} onResolveSuggestion={(accept)=>resolveSuggestion(i, accept)}/>)}
        {streaming && <StreamingBubble theme={theme} text={streamText}/>}
      </div>
      <div style={{ padding: '8px 16px 14px', display: 'flex', flexDirection: 'column', gap: 8 }}>
        <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
          {starters.map(s => (
            <button key={s} className="chrono-tap" onClick={()=>send(s)} disabled={streaming} style={{
              appearance: 'none', cursor: streaming ? 'default' : 'pointer',
              padding: '4px 10px', borderRadius: 999,
              border: `0.5px solid ${theme.hairline}`,
              background: theme.isDark ? 'rgba(255,255,255,0.03)' : 'rgba(10,12,20,0.04)',
              color: theme.textDim, fontFamily: ChronoTokens.font.ui, fontSize: 11, fontWeight: 500,
              opacity: streaming ? 0.5 : 1,
            }}>{s}</button>
          ))}
        </div>
        <div style={{
          display: 'flex', alignItems: 'center', gap: 6,
          padding: 4, borderRadius: 12,
          background: theme.isDark ? 'rgba(20,22,32,0.7)' : '#fff',
          border: `1px solid ${theme.hairline}`,
        }}>
          <input value={input} onChange={e=>setInput(e.target.value)}
            onKeyDown={e => e.key === 'Enter' && send()}
            placeholder="Ask Chrono…"
            maxLength={4000}
            style={{
              flex: 1, appearance: 'none', border: 'none', outline: 'none',
              background: 'transparent', color: theme.text,
              fontFamily: ChronoTokens.font.ui, fontSize: 13, padding: '6px 10px',
            }}/>
          <button className="chrono-tap" onClick={()=>send()} disabled={!input.trim() || streaming} style={{
            appearance: 'none', cursor: 'pointer', border: 'none',
            width: 28, height: 28, borderRadius: 8,
            background: input.trim() ? `linear-gradient(180deg, ${theme.accentColor}, ${theme.accentColor}DD)` : 'transparent',
            color: input.trim() ? '#fff' : theme.textFaint,
            display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
            boxShadow: input.trim() ? theme.accentGlow : 'none',
          }}><Icon.send s={12}/></button>
        </div>
      </div>
    </div>
  );
}

// ── Event details popover ────────────────────────────────────────────────
function DesktopEventDetails({ theme, ev, onClose, onEdit }) {
  const c = ChronoTokens.events[ev.color];
  const [rescheduling, setRescheduling] = React.useState(false);
  // Locate the event's current offset so the reschedule form opens with the right date.
  const currentOff = React.useMemo(() => {
    for (const k of Object.keys(EVENTS_BY_OFFSET)) {
      if (EVENTS_BY_OFFSET[k].indexOf(ev) >= 0) return parseInt(k, 10);
    }
    return 0;
  }, [ev]);
  const initialDate = dateToYMD(dateAtOffset(currentOff));
  const [date, setDate]   = React.useState(initialDate);
  const [start, setStart] = React.useState(ev.start);
  const [end, setEnd]     = React.useState(ev.end);

  const doReschedule = () => {
    const [y, m, d] = date.split('-').map(n => +n);
    const picked = new Date(y, m - 1, d); picked.setHours(0,0,0,0);
    const anchor = new Date(CHRONO_TODAY); anchor.setHours(0,0,0,0);
    const off = Math.round((picked - anchor) / 86400000);
    updateEventByRef(ev, off, { ...ev, start, end });
    onClose();
  };

  const doDelete = () => {
    deleteEventByRef(ev);
    onClose();
  };

  const inputStyle = {
    appearance: 'none', border: `0.5px solid ${theme.hairline}`,
    background: theme.isDark ? 'rgba(255,255,255,0.04)' : 'rgba(10,12,20,0.03)',
    color: theme.text, fontFamily: ChronoTokens.font.mono, fontSize: 12, fontWeight: 600,
    padding: '6px 8px', borderRadius: 8, outline: 'none',
    colorScheme: theme.isDark ? 'dark' : 'light',
  };

  return (
    <div style={{
      position: 'absolute', right: 24, top: 80, width: 320, zIndex: 50,
      padding: 16, borderRadius: 14,
      background: theme.isDark ? 'rgba(20,22,32,0.92)' : 'rgba(255,255,255,0.96)',
      border: `0.5px solid ${theme.hairline}`,
      boxShadow: '0 20px 60px rgba(0,0,0,0.4)',
      backdropFilter: 'blur(40px)',
      animation: `chrono-scale-in 220ms ${Spring.std}`,
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
        <div style={{ width: 4, alignSelf: 'stretch', borderRadius: 2, minHeight: 28,
          background: `linear-gradient(180deg, ${c.base}, ${c.glow})`,
          boxShadow: `0 0 8px ${c.glow}88`,
        }}/>
        <div style={{ flex: 1 }}>
          <div style={{ fontFamily: ChronoTokens.font.display, fontSize: 16, fontWeight: 700, letterSpacing: '-0.01em' }}>{ev.title}</div>
          <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 11, color: theme.textDim }}>{fmtTime(ev.start)} → {fmtTime(ev.end)}</div>
        </div>
        <button className="chrono-tap" onClick={onClose} style={{
          appearance: 'none', cursor: 'pointer', border: 'none', background: 'transparent',
          color: theme.textDim, padding: 4,
        }}><Icon.close s={14}/></button>
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontFamily: ChronoTokens.font.ui, fontSize: 12.5, color: theme.textDim }}>
        <div style={{ display: 'flex', gap: 8 }}><Icon.tag s={14}/> {ev.category || '—'}</div>
        {ev.location && <div style={{ display: 'flex', gap: 8 }}><Icon.pin2 s={14}/> {ev.location}</div>}
        {ev.priority && <div style={{ display: 'flex', gap: 8 }}><Icon.flame s={14}/> {ev.priority} priority</div>}
        {ev.note && <div style={{ display: 'flex', gap: 8 }}><Icon.text s={14}/> {ev.note}</div>}
      </div>

      {rescheduling && (
        <div style={{
          marginTop: 12, padding: 12, borderRadius: 10,
          background: theme.isDark ? 'rgba(255,255,255,0.03)' : 'rgba(10,12,20,0.03)',
          border: `0.5px solid ${theme.hairline}`,
          display: 'flex', flexDirection: 'column', gap: 8,
        }}>
          <div style={{
            fontFamily: ChronoTokens.font.mono, fontSize: 10, fontWeight: 700,
            color: theme.accentColor, letterSpacing: '0.12em', textTransform: 'uppercase',
          }}>Reschedule</div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <Icon.cal s={14}/>
            <input type="date" value={date} onChange={e=>setDate(e.target.value)} style={{ ...inputStyle, flex: 1 }}/>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <Icon.clock s={14}/>
            <input type="time" value={start} onChange={e=>setStart(e.target.value)} style={{ ...inputStyle, flex: 1 }}/>
            <span style={{ color: theme.textFaint }}>→</span>
            <input type="time" value={end} onChange={e=>setEnd(e.target.value)} style={{ ...inputStyle, flex: 1 }}/>
          </div>
        </div>
      )}

      <div style={{ display: 'flex', gap: 6, marginTop: 12, flexWrap: 'wrap' }}>
        {rescheduling ? (
          <>
            <DeskBtn theme={theme} primary onClick={doReschedule}><Icon.check s={12}/> Save</DeskBtn>
            <DeskBtn theme={theme} onClick={()=>{ setRescheduling(false); setDate(initialDate); setStart(ev.start); setEnd(ev.end); }}>Cancel</DeskBtn>
          </>
        ) : (
          <>
            <DeskBtn theme={theme} primary onClick={onEdit}><Icon.edit s={12}/> Edit</DeskBtn>
            <DeskBtn theme={theme} onClick={()=>setRescheduling(true)}><Icon.loop s={12}/> Reschedule</DeskBtn>
            <div style={{ flex: 1 }}/>
            <button className="chrono-tap" onClick={doDelete} title="Delete event"
              style={{
                appearance: 'none', cursor: 'pointer',
                display: 'inline-flex', alignItems: 'center', gap: 6,
                padding: '6px 10px', borderRadius: 8,
                border: `0.5px solid ${ChronoTokens.events.red.base}55`,
                background: `${ChronoTokens.events.red.base}1A`,
                color: ChronoTokens.events.red.base,
                fontFamily: ChronoTokens.font.ui, fontSize: 12, fontWeight: 600,
              }}>
              <Icon.trash s={12}/> Delete
            </button>
          </>
        )}
      </div>
    </div>
  );
}

// ── Event Composer Modal (Manual New event) ────────────────────────────
function DesktopEventComposer({ theme, seed, onClose }) {
  const initial = seed || {};
  const settings = useSettings();
  const categoryList = useCategoryList(); // re-render when categories are added/removed
  // Resolve initial category: explicit on seed, else the category whose color
  // matches the seed. A brand-new event (or one with no matching category)
  // stays UNCATEGORIZED (null) and renders grey.
  const initialCategory = React.useMemo(() => {
    if (initial.category && CATEGORIES.some(c => c.id === initial.category)) return initial.category;
    if (initial.color) {
      const m = CATEGORIES.find(c => c.color === initial.color);
      if (m) return m.id;
    }
    return null;
  }, []);
  const [category, setCategoryRaw] = React.useState(initialCategory);
  const [title, setTitle] = React.useState(initial.title || '');
  const [color, setColor] = React.useState(
    initial.color || CATEGORIES.find(c => c.id === initialCategory)?.color || 'grey');
  // Track whether the user has overridden the auto-color from the category.
  // While false, swapping categories also swaps color.
  const [colorOverridden, setColorOverridden] = React.useState(!!initial.color && initial.category && CATEGORIES.find(c => c.id === initial.category)?.color !== initial.color);
  const setCategory = (id) => {
    setCategoryRaw(id);
    if (!colorOverridden) {
      const c = CATEGORIES.find(c => c.id === id);
      setColor(c ? c.color : 'grey'); // null category → grey
    }
  };
  const setColorByUser = (k) => { setColor(k); setColorOverridden(true); };
  const [allDay, setAllDay] = React.useState(false);
  const [date, setDate] = React.useState(() => dateToYMD(new Date(CHRONO_TODAY)));
  const [start, setStart] = React.useState(initial.start || '09:00');
  const [end, setEnd]     = React.useState(initial.end   || (() => {
    const startMin = parseHM(initial.start || '09:00');
    return fmtHM(startMin + (settings.defaultDuration || 60));
  })());
  const [location, setLocation] = React.useState(initial.location || '');
  const [note, setNote]   = React.useState(initial.note || '');
  const [priority, setPriority] = React.useState(initial.priority || 'normal');
  const [repeat, setRepeat] = React.useState('none');
  const [reminder, setReminder] = React.useState('10');
  const [closing, setClosing] = React.useState(false);

  const titleRef = React.useRef(null);
  // Focus the title ONCE on open. (Previously this lived in the keydown effect
  // below with no dep array, so it re-ran on every render and yanked the cursor
  // back to the title whenever you typed in Notes or any other field.)
  React.useEffect(() => { titleRef.current?.focus(); }, []);
  // Keydown shortcuts — no dep array so doClose/doSave always see current state.
  React.useEffect(() => {
    const onKey = e => {
      if (e.key === 'Escape') doClose();
      if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') doSave();
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  });

  const doClose = () => { setClosing(true); setTimeout(onClose, 160); };
  const doSave = () => {
    const d = ymdToDate(date); d.setHours(0,0,0,0);
    const anchor = new Date(CHRONO_TODAY); anchor.setHours(0,0,0,0);
    const off = Math.round((d - anchor) / 86400000);
    const ev = {
      title: title.trim() || 'Untitled',
      start: allDay ? '00:00' : start,
      end:   allDay ? '23:59' : end,
      color, priority,
      category,
      location: location.trim() || undefined,
      note: note.trim() || undefined,
    };
    if (seed) updateEventByRef(seed, off, ev);
    else      addEventAtOffset(off, ev);
    doClose();
  };

  const colors = ['blue','purple','pink','orange','yellow','green','red'];
  const c = ChronoTokens.events[color];

  return (
    <div onClick={doClose} style={{
      position: 'absolute', inset: 0, zIndex: 100,
      background: theme.isDark ? 'rgba(4,5,10,0.55)' : 'rgba(15,18,30,0.35)',
      backdropFilter: 'blur(8px)', WebkitBackdropFilter: 'blur(8px)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      animation: closing
        ? `chrono-fade-in 160ms ease reverse forwards`
        : `chrono-backdrop-in 220ms ease both`,
    }}>
      <div onClick={e=>e.stopPropagation()} style={{
        width: 600, maxHeight: '90%', overflow: 'auto',
        borderRadius: 18,
        background: theme.isDark ? 'rgba(20,22,32,0.96)' : 'rgba(255,255,255,0.98)',
        border: `0.5px solid ${theme.hairline}`,
        boxShadow: theme.isDark
          ? `0 30px 80px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.04), 0 0 60px ${theme.accentColor}22`
          : `0 30px 80px rgba(10,12,20,0.18), 0 0 0 1px rgba(10,12,20,0.04)`,
        backdropFilter: 'blur(40px)',
        animation: closing
          ? `chrono-modal-in 200ms ${Spring.snap} reverse forwards`
          : `chrono-modal-in 320ms ${Spring.std} both`,
        position: 'relative',
      }} className="chrono-scrollbar">
        {/* Accent gradient strip top */}
        <div style={{
          position: 'absolute', top: 0, left: 0, right: 0, height: 3,
          background: `linear-gradient(90deg, transparent, ${c.glow}, ${theme.accentColor}, ${c.glow}, transparent)`,
          opacity: 0.85,
          borderTopLeftRadius: 18, borderTopRightRadius: 18,
        }}/>

        {/* Header */}
        <div style={{
          padding: '20px 22px 14px',
          borderBottom: `0.5px solid ${theme.hairline}`,
          display: 'flex', alignItems: 'flex-start', gap: 12,
        }}>
          <div style={{
            width: 4, alignSelf: 'stretch', borderRadius: 2,
            background: `linear-gradient(180deg, ${c.base}, ${c.glow})`,
            boxShadow: `0 0 12px ${c.glow}99`,
          }}/>
          <div style={{ flex: 1 }}>
            <div style={{
              fontFamily: ChronoTokens.font.mono, fontSize: 10.5, fontWeight: 600,
              color: theme.textFaint, letterSpacing: '0.14em', textTransform: 'uppercase',
              marginBottom: 4,
            }}>{seed ? 'Edit event' : 'New event'}</div>
            <input ref={titleRef} value={title} onChange={e=>setTitle(e.target.value)}
              placeholder="Event title…"
              style={{
                width: '100%', appearance: 'none', border: 'none', outline: 'none',
                background: 'transparent', color: theme.text,
                fontFamily: ChronoTokens.font.display, fontSize: 22, fontWeight: 700,
                letterSpacing: '-0.025em', padding: 0,
              }}/>
          </div>
          <button className="chrono-tap chrono-btn" onClick={doClose} style={{
            appearance: 'none', cursor: 'pointer', border: 'none',
            width: 28, height: 28, borderRadius: 8,
            background: theme.isDark ? 'rgba(255,255,255,0.05)' : 'rgba(10,12,20,0.05)',
            color: theme.textDim, display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
          }}><Icon.close s={14}/></button>
        </div>

        {/* Category row */}
        <ComposerRow theme={theme} label="Category" icon={<Icon.tag s={13}/>}>
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
            {/* None — uncategorized, renders grey. */}
            {(() => {
              const sel = !category;
              const gc = ChronoTokens.events.grey;
              return (
                <button className="chrono-tap" onClick={()=>setCategory(null)} style={{
                  appearance: 'none', cursor: 'pointer',
                  padding: '5px 11px', borderRadius: 999,
                  border: `0.5px solid ${sel ? gc.glow : theme.hairline}`,
                  background: sel
                    ? (theme.isDark ? `${gc.glow}26` : `${gc.glow}1A`)
                    : (theme.isDark ? 'rgba(255,255,255,0.03)' : 'rgba(10,12,20,0.03)'),
                  color: sel ? (theme.isDark ? '#fff' : gc.glow) : theme.textDim,
                  fontFamily: ChronoTokens.font.ui, fontSize: 12, fontWeight: 600,
                  display: 'inline-flex', alignItems: 'center', gap: 6,
                  transition: 'all 160ms ease',
                }}>
                  <span style={{ width: 7, height: 7, borderRadius: 2, background: `linear-gradient(135deg, ${gc.base}, ${gc.glow})` }}/>
                  None
                </button>
              );
            })()}
            {categoryList.map(cat => {
              const ec = ChronoTokens.events[cat.color];
              const sel = category === cat.id;
              return (
                <button key={cat.id} className="chrono-tap" onClick={()=>setCategory(cat.id)} style={{
                  appearance: 'none', cursor: 'pointer',
                  padding: '5px 11px', borderRadius: 999,
                  border: `0.5px solid ${sel ? ec.glow : theme.hairline}`,
                  background: sel
                    ? (theme.isDark ? `${ec.glow}26` : `${ec.glow}1A`)
                    : (theme.isDark ? 'rgba(255,255,255,0.03)' : 'rgba(10,12,20,0.03)'),
                  color: sel ? (theme.isDark ? '#fff' : ec.glow) : theme.textDim,
                  fontFamily: ChronoTokens.font.ui, fontSize: 12, fontWeight: 600,
                  display: 'inline-flex', alignItems: 'center', gap: 6,
                  boxShadow: sel ? `0 0 12px ${ec.glow}55` : 'none',
                  transition: 'all 160ms ease',
                }}>
                  <span style={{
                    width: 7, height: 7, borderRadius: 2,
                    background: `linear-gradient(135deg, ${ec.base}, ${ec.glow})`,
                  }}/>
                  {cat.name}
                </button>
              );
            })}
          </div>
        </ComposerRow>

        {/* Color row */}
        <ComposerRow theme={theme} label="Color" icon={<Icon.tag s={13}/>}>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            {colors.map(k => {
              const ec = ChronoTokens.events[k];
              const sel = color === k;
              return (
                <button key={k} className="chrono-tap" onClick={()=>setColorByUser(k)} style={{
                  appearance: 'none', cursor: 'pointer', border: 'none',
                  width: 28, height: 28, borderRadius: 9,
                  background: `linear-gradient(135deg, ${ec.base}, ${ec.glow})`,
                  boxShadow: sel
                    ? `0 0 0 2px ${theme.bg0}, 0 0 0 3.5px ${ec.glow}, 0 0 14px ${ec.glow}88`
                    : `0 1px 2px rgba(0,0,0,0.15)`,
                  transition: `transform 200ms ${Spring.snap}, box-shadow 200ms ease`,
                  transform: sel ? 'scale(1.08)' : 'scale(1)',
                }} title={k}/>
              );
            })}
          </div>
        </ComposerRow>

        {/* Date / time — 7-day strip + start/end */}
        <ComposerRow theme={theme} label="When" icon={<Icon.clock s={13}/>}>
          <ChronoTimePanel theme={theme}
            date={date} setDate={setDate}
            start={start} setStart={setStart}
            end={end} setEnd={setEnd}
            allDay={allDay}
          />
        </ComposerRow>

        {/* Options — all-day + recurrence */}
        <ComposerRow theme={theme} label="Options" icon={<Icon.loop s={13}/>}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
            <ComposerToggle theme={theme} value={allDay} onChange={setAllDay} label="All day"/>
            <ComposerSegmented theme={theme} value={repeat} onChange={setRepeat}
              options={[
                { v: 'none',    label: 'No repeat' },
                { v: 'daily',   label: 'Daily' },
                { v: 'weekly',  label: 'Weekly' },
                { v: 'monthly', label: 'Monthly' },
              ]}/>
          </div>
        </ComposerRow>

        {/* Location */}
        <ComposerRow theme={theme} label="Location" icon={<Icon.pin2 s={13}/>}>
          <ComposerField theme={theme}>
            <input value={location} onChange={e=>setLocation(e.target.value)}
              placeholder="Add location, video link, or address"
              style={composerInputStyle(theme)}/>
          </ComposerField>
        </ComposerRow>

        {/* Priority */}
        <ComposerRow theme={theme} label="Priority" icon={<Icon.flame s={13}/>}>
          <ComposerSegmented theme={theme} value={priority} onChange={setPriority}
            options={[
              { v: 'low',    label: 'Low' },
              { v: 'normal', label: 'Normal' },
              { v: 'high',   label: 'High' },
            ]}/>
        </ComposerRow>

        {/* Reminder */}
        <ComposerRow theme={theme} label="Reminder" icon={<Icon.bell s={13}/>}>
          <ComposerSegmented theme={theme} value={reminder} onChange={setReminder}
            options={[
              { v: 'none', label: 'None' },
              { v: '5',    label: '5m' },
              { v: '10',   label: '10m' },
              { v: '30',   label: '30m' },
              { v: '60',   label: '1h' },
            ]}/>
        </ComposerRow>

        {/* Notes */}
        <ComposerRow theme={theme} label="Notes" icon={<Icon.text s={13}/>}>
          <ComposerField theme={theme}>
            <textarea value={note} onChange={e=>setNote(e.target.value)}
              rows={3} placeholder="Add notes, agenda, or links…"
              style={{...composerInputStyle(theme), resize: 'vertical', minHeight: 64, lineHeight: 1.45 }}/>
          </ComposerField>
        </ComposerRow>

        {/* Footer */}
        <div style={{
          padding: '14px 22px 18px',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12,
          borderTop: `0.5px solid ${theme.hairline}`,
        }}>
          <div style={{
            fontFamily: ChronoTokens.font.mono, fontSize: 10.5, color: theme.textFaint,
            letterSpacing: '0.06em',
          }}>
            <span style={{
              padding: '2px 6px', borderRadius: 4,
              background: theme.isDark ? 'rgba(255,255,255,0.06)' : 'rgba(10,12,20,0.06)',
              fontWeight: 600,
            }}>⌘ ↵</span>  to save  ·  <span style={{
              padding: '2px 6px', borderRadius: 4,
              background: theme.isDark ? 'rgba(255,255,255,0.06)' : 'rgba(10,12,20,0.06)',
              fontWeight: 600,
            }}>esc</span>  to cancel
          </div>
          <div style={{ display: 'flex', gap: 8 }}>
            <DeskBtn theme={theme} onClick={doClose}>Cancel</DeskBtn>
            <DeskBtn theme={theme} primary onClick={doSave}>
              <Icon.check s={12}/> {seed ? 'Save changes' : 'Create event'}
            </DeskBtn>
          </div>
        </div>
      </div>
    </div>
  );
}

// ── Custom futuristic chronograph time panel ───────────────────────────
// Helpers
function parseHM(s) {
  if (!s) return 0;
  const [h, m] = s.split(':').map(n => parseInt(n, 10) || 0);
  return h * 60 + m;
}
function fmtHM(min) {
  min = ((min % 1440) + 1440) % 1440;
  const h = Math.floor(min / 60), m = min % 60;
  return `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}`;
}
function fmt12(min) {
  min = ((min % 1440) + 1440) % 1440;
  let h = Math.floor(min / 60), m = min % 60;
  const ampm = h >= 12 ? 'PM' : 'AM';
  h = h % 12 || 12;
  return `${h}:${String(m).padStart(2,'0')} ${ampm}`;
}
function ymdToDate(s) { const [y,m,d] = s.split('-').map(n=>+n); return new Date(y, m-1, d); }
function dateToYMD(d) { return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; }
function isSameYMD(a,b) { return a.getFullYear()===b.getFullYear() && a.getMonth()===b.getMonth() && a.getDate()===b.getDate(); }

function ChronoTimePanel({ theme, date, setDate, start, setStart, end, setEnd, allDay }) {
  const startMin = parseHM(start);
  const endMin   = parseHM(end);
  const duration = ((endMin - startMin) % 1440 + 1440) % 1440;

  // — Date tape: current week (Mon→Sun), today highlighted, no scroll ——
  const today = new Date(); today.setHours(0,0,0,0);
  const sel = ymdToDate(date);
  const dayList = React.useMemo(() => {
    const weekStart = new Date(today);
    const dow = today.getDay();
    const offsetToMonday = dow === 0 ? -6 : 1 - dow;
    weekStart.setDate(today.getDate() + offsetToMonday);
    return Array.from({ length: 7 }, (_, i) => {
      const d = new Date(weekStart);
      d.setDate(weekStart.getDate() + i);
      return d;
    });
  }, []);

  return (
    <div style={{
      display: 'flex', flexDirection: 'column', gap: 14,
      padding: 14, borderRadius: 14,
      background: theme.isDark
        ? `linear-gradient(180deg, ${theme.accentColor}0F, ${theme.signal || theme.accentColor}08), rgba(0,0,0,0.25)`
        : `linear-gradient(180deg, ${theme.accentColor}10, transparent), rgba(255,255,255,0.6)`,
      border: `0.5px solid ${theme.hairline}`,
      position: 'relative', overflow: 'hidden',
      opacity: allDay ? 0.55 : 1,
      transition: 'opacity 220ms ease',
    }}>
      {/* Faint scanlines */}
      <div style={{
        position: 'absolute', inset: 0, pointerEvents: 'none',
        background: `repeating-linear-gradient(0deg, ${theme.isDark ? 'rgba(255,255,255,0.025)' : 'rgba(10,12,20,0.02)'} 0 1px, transparent 1px 4px)`,
        maskImage: 'radial-gradient(ellipse 100% 80% at 50% 50%, black 30%, transparent 100%)',
        WebkitMaskImage: 'radial-gradient(ellipse 100% 80% at 50% 50%, black 30%, transparent 100%)',
      }}/>

      {/* Date tape — 7 days, current week */}
      <DateTape theme={theme} dayList={dayList} sel={sel} setDate={(d)=>setDate(dateToYMD(d))}/>

      {/* Chronograph dial — drag start/end handles */}
      <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', position: 'relative' }}>
        <ChronoDial theme={theme} startMin={startMin} endMin={endMin} disabled={allDay}
          onChangeStart={(m)=>setStart(fmtHM(m))}
          onChangeEnd={(m)=>setEnd(fmtHM(m))}
        />
      </div>

      {/* Time readouts — start + end side by side */}
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
        <TimeReadout theme={theme} label="Start" min={startMin} onChange={(m)=>setStart(fmtHM(m))} disabled={allDay} accent={theme.accentColor}/>
        <TimeReadout theme={theme} label="End"   min={endMin}   onChange={(m)=>setEnd(fmtHM(m))}   disabled={allDay} accent={theme.signal || theme.accentColor}/>
      </div>
    </div>
  );
}

function DateTape({ theme, dayList, sel, setDate }) {
  const today = new Date(); today.setHours(0,0,0,0);
  return (
    <div style={{ display: 'flex', gap: 6, padding: '4px 2px' }}>
      {dayList.map((d, i) => {
        const a = isSameYMD(d, sel);
        const isToday = isSameYMD(d, today);
        return (
          <button key={i} data-selected={a ? '1' : '0'} className="chrono-tap" onClick={()=>setDate(d)} style={{
            appearance: 'none', cursor: 'pointer', border: 'none',
            flex: '1 1 0', minWidth: 0,
            padding: '8px 0 9px', borderRadius: 10,
            display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1,
            position: 'relative',
            background: a
              ? `linear-gradient(180deg, ${theme.accentColor}33, ${theme.accentColor}14)`
              : (theme.isDark ? 'rgba(255,255,255,0.03)' : 'rgba(10,12,20,0.03)'),
            border: `0.5px solid ${a ? theme.accentColor + '66' : theme.hairline}`,
            boxShadow: a ? `0 0 0 1px ${theme.accentColor}33, 0 0 14px ${theme.accentColor}33` : 'none',
            color: a ? theme.text : theme.textDim,
            transition: `all 200ms ${Spring.snap}`,
            transform: a ? 'translateY(-1px)' : 'none',
          }}>
            <span style={{
              fontFamily: ChronoTokens.font.mono, fontSize: 9.5, fontWeight: 600,
              color: a ? theme.accentColor : theme.textFaint,
              letterSpacing: '0.14em', textTransform: 'uppercase',
            }}>{d.toLocaleDateString(undefined, { weekday: 'short' }).slice(0,3)}</span>
            <span style={{
              fontFamily: ChronoTokens.font.display, fontSize: 18, fontWeight: 700,
              letterSpacing: '-0.02em', lineHeight: 1.1,
              color: a ? theme.text : (isToday ? theme.accentColor : theme.text),
            }}>{d.getDate()}</span>
            <span style={{
              fontFamily: ChronoTokens.font.mono, fontSize: 8.5, fontWeight: 600,
              color: theme.textFaint, letterSpacing: '0.1em', textTransform: 'uppercase',
            }}>{d.toLocaleDateString(undefined, { month: 'short' })}</span>
            {isToday && (
              <span style={{
                position: 'absolute', bottom: 3,
                width: 4, height: 4, borderRadius: 999,
                background: theme.accentColor,
                boxShadow: `0 0 6px ${theme.accentColor}`,
              }}/>
            )}
          </button>
        );
      })}
    </div>
  );
}

// 24h chronograph dial. Start handle = accent. End handle = signal cyan. Drag either.
function ChronoDial({ theme, startMin, endMin, onChangeStart, onChangeEnd, disabled }) {
  const SIZE = 200;
  const CX = SIZE/2, CY = SIZE/2;
  const R_OUT = 88;
  const R_IN  = 66;
  const R_ARC = 77;
  const ref = React.useRef(null);
  const [dragging, setDragging] = React.useState(null); // 'start' | 'end' | null

  const sig = theme.signal || theme.accentColor;
  const a = theme.accentColor;

  // Convert minute → angle (degrees) — 24h ring, 0min = top, clockwise
  // 1440 min → 360°
  const minToAngle = (m) => (m / 1440) * 360 - 90;
  const angleToMin = (deg) => {
    let m = ((deg + 90) / 360) * 1440;
    m = Math.round(m / 5) * 5; // 5-min snap
    return ((m % 1440) + 1440) % 1440;
  };
  const handlePos = (m) => {
    const rad = minToAngle(m) * Math.PI / 180;
    return { x: CX + Math.cos(rad) * R_ARC, y: CY + Math.sin(rad) * R_ARC };
  };
  const startPt = handlePos(startMin);
  const endPt   = handlePos(endMin);

  // Arc path from start angle to end angle (going clockwise)
  const arcPath = React.useMemo(() => {
    const r = R_ARC;
    let a1 = minToAngle(startMin);
    let a2 = minToAngle(endMin);
    let sweep = a2 - a1;
    if (sweep <= 0) sweep += 360;
    const large = sweep > 180 ? 1 : 0;
    const x1 = CX + Math.cos(a1*Math.PI/180)*r;
    const y1 = CY + Math.sin(a1*Math.PI/180)*r;
    const x2 = CX + Math.cos(a2*Math.PI/180)*r;
    const y2 = CY + Math.sin(a2*Math.PI/180)*r;
    return `M ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2}`;
  }, [startMin, endMin]);

  React.useEffect(() => {
    if (!dragging || disabled) return;
    const onMove = (e) => {
      const rect = ref.current.getBoundingClientRect();
      const cx = rect.left + rect.width/2;
      const cy = rect.top + rect.height/2;
      const pt = e.touches ? e.touches[0] : e;
      const dx = pt.clientX - cx, dy = pt.clientY - cy;
      const deg = Math.atan2(dy, dx) * 180 / Math.PI;
      const m = angleToMin(deg);
      if (dragging === 'start') onChangeStart(m);
      else onChangeEnd(m);
    };
    const onUp = () => setDragging(null);
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onUp);
    window.addEventListener('touchmove', onMove, { passive: false });
    window.addEventListener('touchend', onUp);
    return () => {
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('mouseup', onUp);
      window.removeEventListener('touchmove', onMove);
      window.removeEventListener('touchend', onUp);
    };
  }, [dragging, disabled, onChangeStart, onChangeEnd]);

  // Tick marks: 24 hours, with 6/12/18 highlighted
  const ticks = [];
  for (let i = 0; i < 24; i++) {
    const ang = (i / 24) * 360 - 90;
    const major = i % 6 === 0;
    const x1 = CX + Math.cos(ang*Math.PI/180) * (major ? R_OUT - 4 : R_OUT - 2);
    const y1 = CY + Math.sin(ang*Math.PI/180) * (major ? R_OUT - 4 : R_OUT - 2);
    const x2 = CX + Math.cos(ang*Math.PI/180) * R_OUT;
    const y2 = CY + Math.sin(ang*Math.PI/180) * R_OUT;
    ticks.push({ x1,y1,x2,y2, major, hour: i });
  }

  // Center label: duration mm:ss
  const dur = ((endMin - startMin) % 1440 + 1440) % 1440;
  const dh = Math.floor(dur/60), dm = dur%60;

  return (
    <div ref={ref} style={{
      width: SIZE, height: SIZE, position: 'relative',
      filter: disabled ? 'grayscale(0.6) opacity(0.6)' : 'none',
    }}>
      <svg width={SIZE} height={SIZE} style={{ overflow: 'visible' }}>
        <defs>
          <radialGradient id="dial-bg" cx="0.5" cy="0.5" r="0.55">
            <stop offset="0" stopColor={theme.isDark ? '#0d1020' : '#ffffff'}/>
            <stop offset="1" stopColor={theme.isDark ? '#06070C' : '#eef0f6'}/>
          </radialGradient>
          <linearGradient id="dial-arc" x1="0" y1="0" x2="1" y2="1">
            <stop offset="0" stopColor={a}/>
            <stop offset="1" stopColor={sig}/>
          </linearGradient>
        </defs>
        {/* Outer ring background */}
        <circle cx={CX} cy={CY} r={R_OUT} fill="url(#dial-bg)"
          stroke={theme.hairline} strokeWidth="1"/>
        {/* Inner ring */}
        <circle cx={CX} cy={CY} r={R_IN} fill="none"
          stroke={theme.hairline} strokeWidth="0.5"/>
        {/* Tick marks */}
        {ticks.map((t, i) => (
          <line key={i} x1={t.x1} y1={t.y1} x2={t.x2} y2={t.y2}
            stroke={t.major ? (theme.isDark ? '#fff' : theme.text) : theme.textFaint}
            strokeWidth={t.major ? 1.5 : 0.75}
            opacity={t.major ? 0.55 : 0.35}/>
        ))}
        {/* Hour numerals 0,6,12,18 */}
        {[0,6,12,18].map((h,i) => {
          const ang = (h / 24) * 360 - 90;
          const r = R_OUT - 12;
          const x = CX + Math.cos(ang*Math.PI/180)*r;
          const y = CY + Math.sin(ang*Math.PI/180)*r;
          return (
            <text key={i} x={x} y={y} textAnchor="middle" dominantBaseline="central"
              fontFamily={ChronoTokens.font.mono} fontSize="8" fontWeight="700"
              fill={theme.textFaint}
              style={{ letterSpacing: '0.1em' }}>{String(h).padStart(2,'0')}</text>
          );
        })}
        {/* Active arc */}
        <path d={arcPath} stroke="url(#dial-arc)" strokeWidth="4" fill="none" strokeLinecap="round"
          style={{ filter: `drop-shadow(0 0 6px ${a}AA)` }}/>
        {/* Start handle */}
        <g style={{ cursor: disabled ? 'default' : 'grab' }}
           onMouseDown={(e)=>{ if(!disabled){ e.preventDefault(); setDragging('start'); }}}
           onTouchStart={(e)=>{ if(!disabled){ setDragging('start'); }}}>
          <circle cx={startPt.x} cy={startPt.y} r="11" fill="transparent"/>
          <circle cx={startPt.x} cy={startPt.y} r="6" fill={a}
            style={{ filter: `drop-shadow(0 0 8px ${a})` }}/>
          <circle cx={startPt.x} cy={startPt.y} r="2.5" fill="#fff"/>
        </g>
        {/* End handle */}
        <g style={{ cursor: disabled ? 'default' : 'grab' }}
           onMouseDown={(e)=>{ if(!disabled){ e.preventDefault(); setDragging('end'); }}}
           onTouchStart={(e)=>{ if(!disabled){ setDragging('end'); }}}>
          <circle cx={endPt.x} cy={endPt.y} r="11" fill="transparent"/>
          <circle cx={endPt.x} cy={endPt.y} r="6" fill={sig}
            style={{ filter: `drop-shadow(0 0 8px ${sig})` }}/>
          <circle cx={endPt.x} cy={endPt.y} r="2.5" fill="#fff"/>
        </g>
      </svg>
      {/* Center readout */}
      <div style={{
        position: 'absolute', inset: 0, display: 'flex',
        flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
        pointerEvents: 'none',
      }}>
        <div style={{
          fontFamily: ChronoTokens.font.mono, fontSize: 9.5, fontWeight: 600,
          color: theme.textFaint, letterSpacing: '0.18em', textTransform: 'uppercase',
        }}>Duration</div>
        <div style={{
          fontFamily: ChronoTokens.font.display, fontSize: 28, fontWeight: 800,
          color: theme.text, letterSpacing: '-0.03em', lineHeight: 1,
          fontVariantNumeric: 'tabular-nums', marginTop: 2,
        }}>
          {dh > 0 && `${dh}h`}{dh > 0 && dm > 0 && ' '}{dm > 0 && `${dm}m`}{dur === 0 && '0m'}
        </div>
        <div style={{
          fontFamily: ChronoTokens.font.mono, fontSize: 9.5, fontWeight: 600,
          color: theme.textFaint, letterSpacing: '0.14em', marginTop: 4,
        }}>{fmt12(startMin)} → {fmt12(endMin)}</div>
      </div>
    </div>
  );
}

// One time readout — segmented HH : MM with up/down nudge
function TimeReadout({ theme, label, min, onChange, disabled, accent }) {
  const h = Math.floor(min/60), m = min%60;
  const ampm = h >= 12 ? 'PM' : 'AM';
  const h12 = h % 12 || 12;
  const nudge = (delta) => onChange(((min + delta) % 1440 + 1440) % 1440);
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 8, padding: '8px 10px',
      borderRadius: 10,
      background: theme.isDark ? 'rgba(0,0,0,0.25)' : 'rgba(10,12,20,0.03)',
      border: `0.5px solid ${theme.hairline}`,
      boxShadow: `inset 0 0 0 0.5px ${accent}33`,
      opacity: disabled ? 0.5 : 1,
      minWidth: 0,
    }}>
      <span style={{
        width: 6, height: 6, borderRadius: 999, background: accent,
        boxShadow: `0 0 8px ${accent}`, flexShrink: 0,
      }}/>
      <span style={{
        fontFamily: ChronoTokens.font.mono, fontSize: 9.5, fontWeight: 700,
        color: theme.textFaint, letterSpacing: '0.16em', textTransform: 'uppercase',
        flexShrink: 0,
      }}>{label}</span>
      <div style={{ flex: 1, display: 'flex', alignItems: 'baseline', gap: 3, justifyContent: 'flex-end' }}>
        <NudgeNum theme={theme} value={h12} onUp={()=>nudge(60)} onDown={()=>nudge(-60)} disabled={disabled} pad={2}/>
        <span style={{
          fontFamily: ChronoTokens.font.display, fontSize: 20, fontWeight: 700,
          color: theme.textDim, opacity: 0.5,
        }}>:</span>
        <NudgeNum theme={theme} value={m} onUp={()=>nudge(5)} onDown={()=>nudge(-5)} disabled={disabled} pad={2}/>
        <button className="chrono-tap" onClick={()=>nudge(12*60)} disabled={disabled} style={{
          appearance: 'none', cursor: disabled ? 'default' : 'pointer', border: 'none',
          padding: '2px 6px', borderRadius: 6, marginLeft: 4,
          background: theme.isDark ? 'rgba(255,255,255,0.06)' : 'rgba(10,12,20,0.06)',
          color: theme.textDim,
          fontFamily: ChronoTokens.font.mono, fontSize: 9.5, fontWeight: 700,
          letterSpacing: '0.08em', flexShrink: 0,
        }}>{ampm}</button>
      </div>
    </div>
  );
}

function NudgeNum({ theme, value, onUp, onDown, disabled, pad = 2 }) {
  return (
    <div style={{
      display: 'flex', flexDirection: 'column', alignItems: 'stretch',
      position: 'relative', userSelect: 'none',
    }}>
      <button className="chrono-tap" onClick={onUp} disabled={disabled} style={nudgeBtn(theme,'up',disabled)}>
        <svg width="9" height="6" viewBox="0 0 9 6"><path d="M0.5 5.5L4.5 1L8.5 5.5" stroke="currentColor" strokeWidth="1.5" fill="none" strokeLinecap="round"/></svg>
      </button>
      <div style={{
        fontFamily: ChronoTokens.font.display, fontSize: 20, fontWeight: 700,
        color: theme.text, letterSpacing: '-0.02em', lineHeight: 1,
        textAlign: 'center', minWidth: 26,
        fontVariantNumeric: 'tabular-nums',
      }}>{String(value).padStart(pad,'0')}</div>
      <button className="chrono-tap" onClick={onDown} disabled={disabled} style={nudgeBtn(theme,'down',disabled)}>
        <svg width="9" height="6" viewBox="0 0 9 6"><path d="M0.5 0.5L4.5 5L8.5 0.5" stroke="currentColor" strokeWidth="1.5" fill="none" strokeLinecap="round"/></svg>
      </button>
    </div>
  );
}
function nudgeBtn(theme, dir, disabled) {
  return {
    appearance: 'none', cursor: disabled ? 'default' : 'pointer', border: 'none', background: 'transparent',
    color: theme.textFaint, padding: '1px 0',
    display: 'flex', alignItems: 'center', justifyContent: 'center',
    opacity: disabled ? 0.4 : 0.8,
    transition: 'color 160ms ease, opacity 160ms ease',
  };
}

function DurationStrip({ theme, duration, startMin, endMin }) {
  // Visual day-bar showing where the event sits in 24h
  const a = theme.accentColor;
  const sig = theme.signal || a;
  const startPct = (startMin / 1440) * 100;
  let widthPct = (duration / 1440) * 100;
  if (widthPct < 1) widthPct = 1;
  // Wraps midnight?
  const wraps = endMin <= startMin && duration > 0;
  return (
    <div style={{
      position: 'relative', height: 18, borderRadius: 6,
      background: theme.isDark ? 'rgba(0,0,0,0.4)' : 'rgba(10,12,20,0.06)',
      border: `0.5px solid ${theme.hairline}`,
      overflow: 'hidden',
    }}>
      {/* Hour ticks */}
      {Array.from({length: 25}).map((_, i) => (
        <div key={i} style={{
          position: 'absolute', top: 0, bottom: 0,
          left: `${(i/24)*100}%`, width: 1,
          background: theme.hairline,
          opacity: i % 6 === 0 ? 1 : 0.5,
        }}/>
      ))}
      {/* Active band */}
      {!wraps ? (
        <div style={{
          position: 'absolute', top: 2, bottom: 2,
          left: `${startPct}%`, width: `${widthPct}%`,
          background: `linear-gradient(90deg, ${a}, ${sig})`,
          boxShadow: `0 0 10px ${a}88`,
          borderRadius: 4,
        }}/>
      ) : (
        <>
          <div style={{
            position: 'absolute', top: 2, bottom: 2,
            left: `${startPct}%`, right: 2,
            background: `linear-gradient(90deg, ${a}, ${sig})`,
            boxShadow: `0 0 10px ${a}88`, borderRadius: 4,
          }}/>
          <div style={{
            position: 'absolute', top: 2, bottom: 2,
            left: 2, width: `${(endMin/1440)*100}%`,
            background: `linear-gradient(90deg, ${a}, ${sig})`,
            boxShadow: `0 0 10px ${a}88`, borderRadius: 4,
          }}/>
        </>
      )}
      {/* Now indicator */}
      {(() => {
        const now = new Date();
        const nowPct = ((now.getHours()*60 + now.getMinutes()) / 1440) * 100;
        return (
          <div style={{
            position: 'absolute', top: -2, bottom: -2, left: `${nowPct}%`, width: 1,
            background: '#fff', opacity: 0.5,
          }}/>
        );
      })()}
      {/* Hour labels overlay */}
      <div style={{
        position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: '0 4px', pointerEvents: 'none',
        fontFamily: ChronoTokens.font.mono, fontSize: 7.5, fontWeight: 700,
        color: theme.textFaint, letterSpacing: '0.12em', textTransform: 'uppercase',
      }}>
        <span>00</span><span>06</span><span>12</span><span>18</span><span>24</span>
      </div>
    </div>
  );
}

function QuickDurations({ theme, startMin, setEnd, disabled }) {
  const presets = [15, 30, 45, 60, 90, 120];
  return (
    <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
      {presets.map(d => (
        <button key={d} className="chrono-tap" onClick={()=>setEnd((startMin + d) % 1440)} disabled={disabled} style={{
          appearance: 'none', cursor: disabled ? 'default' : 'pointer', border: `0.5px solid ${theme.hairline}`,
          padding: '3px 8px', borderRadius: 6,
          background: theme.isDark ? 'rgba(255,255,255,0.04)' : 'rgba(10,12,20,0.04)',
          color: theme.textDim,
          fontFamily: ChronoTokens.font.mono, fontSize: 10, fontWeight: 700,
          letterSpacing: '0.1em', textTransform: 'uppercase',
          opacity: disabled ? 0.4 : 1,
          transition: 'all 160ms ease',
        }}>+{d < 60 ? `${d}m` : `${d/60}h`}</button>
      ))}
    </div>
  );
}

function ComposerRow({ theme, label, icon, children }) {
  return (
    <div style={{
      padding: '14px 22px',
      borderBottom: `0.5px solid ${theme.hairline}`,
      display: 'grid', gridTemplateColumns: '110px 1fr', gap: 16, alignItems: 'flex-start',
      animation: `chrono-stagger-up 320ms ${Spring.snap} both`,
    }}>
      <div style={{
        display: 'flex', alignItems: 'center', gap: 6,
        fontFamily: ChronoTokens.font.mono, fontSize: 10.5, fontWeight: 600,
        color: theme.textFaint, letterSpacing: '0.12em', textTransform: 'uppercase',
        paddingTop: 8,
      }}>
        <span style={{ color: theme.textDim }}>{icon}</span>
        {label}
      </div>
      <div>{children}</div>
    </div>
  );
}

function ComposerField({ theme, children, disabled }) {
  return (
    <div style={{
      display: 'flex', alignItems: 'center',
      borderRadius: 10,
      background: theme.isDark ? 'rgba(255,255,255,0.04)' : 'rgba(10,12,20,0.03)',
      border: `0.5px solid ${theme.hairline}`,
      padding: '0 10px',
      transition: 'border-color 180ms ease, background-color 180ms ease',
      opacity: disabled ? 0.45 : 1,
    }}>
      {children}
    </div>
  );
}

function composerInputStyle(theme) {
  return {
    flex: 1, width: '100%', appearance: 'none', border: 'none', outline: 'none',
    background: 'transparent', color: theme.text,
    fontFamily: ChronoTokens.font.ui, fontSize: 13, fontWeight: 500,
    padding: '8px 0',
    colorScheme: theme.isDark ? 'dark' : 'light',
  };
}

function ComposerToggle({ theme, value, onChange, label }) {
  return (
    <button className="chrono-tap" onClick={()=>onChange(!value)} style={{
      appearance: 'none', cursor: 'pointer', border: 'none',
      display: 'inline-flex', alignItems: 'center', gap: 8,
      padding: '5px 10px 5px 6px', borderRadius: 999,
      background: value ? `${theme.accentColor}1F` : (theme.isDark ? 'rgba(255,255,255,0.04)' : 'rgba(10,12,20,0.04)'),
      border: `0.5px solid ${value ? theme.accentColor + '55' : theme.hairline}`,
      color: value ? theme.accentColor : theme.textDim,
      fontFamily: ChronoTokens.font.ui, fontSize: 12, fontWeight: 600,
      transition: 'all 200ms ease',
    }}>
      <span style={{
        width: 24, height: 14, borderRadius: 999, position: 'relative',
        background: value ? theme.accentColor : (theme.isDark ? 'rgba(255,255,255,0.18)' : 'rgba(10,12,20,0.2)'),
        transition: 'background-color 200ms ease',
      }}>
        <span style={{
          position: 'absolute', top: 2, left: value ? 12 : 2,
          width: 10, height: 10, borderRadius: 999,
          background: '#fff',
          transition: `left 220ms ${Spring.std}`,
          boxShadow: '0 1px 2px rgba(0,0,0,0.2)',
        }}/>
      </span>
      {label}
    </button>
  );
}

function ComposerSegmented({ theme, value, onChange, options }) {
  return (
    <div style={{
      display: 'inline-flex', padding: 3, borderRadius: 10,
      background: theme.isDark ? 'rgba(255,255,255,0.04)' : 'rgba(10,12,20,0.04)',
      border: `0.5px solid ${theme.hairline}`,
      gap: 2,
    }}>
      {options.map(o => {
        const a = value === o.v;
        return (
          <button key={o.v} className="chrono-tap" onClick={()=>onChange(o.v)} style={{
            appearance: 'none', cursor: 'pointer', border: 'none',
            padding: '5px 10px', borderRadius: 7,
            background: a
              ? (theme.isDark ? `linear-gradient(180deg, ${theme.accentColor}, ${theme.accentColor}DD)` : '#fff')
              : 'transparent',
            color: a ? (theme.isDark ? '#fff' : theme.accentColor) : theme.textDim,
            fontFamily: ChronoTokens.font.ui, fontSize: 11.5, fontWeight: 600,
            letterSpacing: '-0.005em',
            boxShadow: a
              ? (theme.isDark ? theme.accentGlow : '0 1px 2px rgba(0,0,0,0.06)')
              : 'none',
            transition: 'all 180ms ease',
          }}>{o.label}</button>
        );
      })}
    </div>
  );
}

// ── Categories (full screen) ────────────────────────────────────────────
const CATEGORY_COLOR_CHOICES = ['blue','purple','green','orange','yellow','pink','red'];

function CategoryCreator({ theme, onDone }) {
  const [name, setName] = React.useState('');
  const [color, setColor] = React.useState('blue');
  const inputRef = React.useRef(null);
  React.useEffect(() => { inputRef.current?.focus(); }, []);
  const create = () => {
    const trimmed = name.trim();
    if (!trimmed) return;
    addCategory({ name: trimmed, color });
    onDone();
  };
  return (
    <div style={{
      padding: 16, borderRadius: 12, marginBottom: 4,
      background: theme.isDark ? 'rgba(20,22,32,0.6)' : 'rgba(255,255,255,0.85)',
      border: `0.5px solid ${theme.accentColor}55`,
      boxShadow: `0 0 20px ${theme.accentColor}22`,
      display: 'flex', flexDirection: 'column', gap: 12,
    }}>
      <input ref={inputRef} value={name} onChange={e=>setName(e.target.value)}
        onKeyDown={e => { if (e.key === 'Enter') create(); if (e.key === 'Escape') onDone(); }}
        placeholder="Category name…" maxLength={40}
        style={{
          appearance: 'none', border: 'none', outline: 'none', background: 'transparent',
          color: theme.text, fontFamily: ChronoTokens.font.display, fontSize: 18, fontWeight: 700,
          letterSpacing: '-0.01em',
        }}/>
      <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
        {CATEGORY_COLOR_CHOICES.map(k => {
          const ec = ChronoTokens.events[k];
          const sel = color === k;
          return (
            <button key={k} className="chrono-tap" onClick={()=>setColor(k)} title={k} style={{
              appearance: 'none', cursor: 'pointer', width: 22, height: 22, borderRadius: 7,
              background: `linear-gradient(135deg, ${ec.base}, ${ec.glow})`,
              border: sel ? `2px solid ${theme.text}` : `2px solid transparent`,
              boxShadow: sel ? `0 0 10px ${ec.glow}88` : 'none',
            }}/>
          );
        })}
        <div style={{ flex: 1 }}/>
        <DeskBtn theme={theme} onClick={onDone}>Cancel</DeskBtn>
        <DeskBtn theme={theme} primary onClick={create}><Icon.plus s={12}/> Create</DeskBtn>
      </div>
    </div>
  );
}

function DesktopCategories({ theme }) {
  const [editing, setEditing] = React.useState(null);
  const [creating, setCreating] = React.useState(false);
  const cats = useCategories();
  const totalHours = cats.reduce((s, c) => s + c.hours, 0);
  const totalForBar = cats.reduce((s, c) => s + c.count, 0) || 1;
  return (
    <div style={{ position: 'absolute', inset: 0, overflow: 'auto', padding: 32 }} className="chrono-scrollbar">
      <DesktopHeader theme={theme} eyebrow="Workspace" title="Categories"
        right={<DeskBtn theme={theme} primary onClick={()=>setCreating(c=>!c)}><Icon.plus s={12}/> New category</DeskBtn>}/>
      <div style={{ marginTop: 24, display: 'grid', gridTemplateColumns: '2fr 1fr', gap: 24, maxWidth: 1100 }}>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          {creating && <CategoryCreator theme={theme} onDone={()=>setCreating(false)}/>}
          {cats.length === 0 && !creating && (
            <div style={{
              padding: '40px 24px', borderRadius: 12, textAlign: 'center',
              border: `0.5px dashed ${theme.hairline}`,
              background: theme.isDark ? 'rgba(20,22,32,0.4)' : 'rgba(255,255,255,0.5)',
              color: theme.textDim, fontFamily: ChronoTokens.font.ui, fontSize: 13.5, lineHeight: 1.5,
            }}>
              No categories yet. Create one above, or just ask the assistant to schedule
              something — it'll offer to make a fitting category for you.
            </div>
          )}
          {cats.map(c => {
            const ec = ChronoTokens.events[c.color];
            const sel = editing === c.id;
            return (
              <div key={c.id} className="chrono-tap" onClick={()=>setEditing(sel ? null : c.id)} style={{
                padding: 16, borderRadius: 12,
                background: sel
                  ? (theme.isDark ? `${ec.glow}1A` : `${ec.glow}10`)
                  : (theme.isDark ? 'rgba(20,22,32,0.5)' : 'rgba(255,255,255,0.7)'),
                border: `0.5px solid ${sel ? ec.glow + '66' : theme.hairline}`,
                boxShadow: sel ? `0 0 20px ${ec.glow}33` : 'none',
                cursor: 'pointer', transition: 'all 200ms ease',
                display: 'flex', alignItems: 'center', gap: 14,
              }}>
                <div style={{
                  width: 36, height: 36, borderRadius: 10,
                  background: `linear-gradient(135deg, ${ec.base}, ${ec.glow})`,
                  boxShadow: `0 0 16px ${ec.glow}66`,
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  color: '#fff', fontFamily: ChronoTokens.font.mono, fontSize: 11, fontWeight: 700,
                }}>{c.name.slice(0,2).toUpperCase()}</div>
                <div style={{ flex: 1 }}>
                  <div style={{
                    fontFamily: ChronoTokens.font.display, fontSize: 16, fontWeight: 700,
                    letterSpacing: '-0.01em', marginBottom: 2,
                  }}>{c.name}</div>
                  <div style={{
                    fontFamily: ChronoTokens.font.mono, fontSize: 11, color: theme.textFaint,
                    letterSpacing: '0.04em',
                  }}>{c.count} {c.count === 1 ? 'event' : 'events'} · {c.hours}h scheduled</div>
                </div>
                <div style={{ display: 'flex', gap: 8 }}>
                  <button className="chrono-tap chrono-btn" style={{
                    appearance: 'none', cursor: 'pointer', border: `0.5px solid ${theme.hairline}`,
                    padding: '6px 10px', borderRadius: 8, background: 'transparent',
                    color: theme.textDim, fontSize: 11.5, fontWeight: 600,
                  }}>Edit</button>
                  <button className="chrono-tap chrono-btn"
                    onClick={(e)=>{ e.stopPropagation(); removeCategory(c.id); if (editing === c.id) setEditing(null); }}
                    title="Delete category" style={{
                    appearance: 'none', cursor: 'pointer', border: 'none',
                    padding: '6px 10px', borderRadius: 8,
                    background: theme.isDark ? 'rgba(255,255,255,0.05)' : 'rgba(10,12,20,0.05)',
                    color: theme.textDim, fontSize: 11.5, fontWeight: 600,
                  }}><Icon.close s={11}/></button>
                </div>
              </div>
            );
          })}
        </div>
        <div style={{
          padding: 22, borderRadius: 14, height: 'fit-content',
          background: theme.isDark
            ? `linear-gradient(135deg, ${theme.accentColor}14, transparent 60%), rgba(20,22,32,0.5)`
            : `linear-gradient(135deg, ${theme.accentColor}10, transparent 60%), rgba(255,255,255,0.7)`,
          border: `0.5px solid ${theme.hairline}`,
        }}>
          <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 10.5, fontWeight: 700, color: theme.accentColor, letterSpacing: '0.18em', textTransform: 'uppercase', marginBottom: 10 }}>Scheduled</div>
          <div style={{ fontFamily: ChronoTokens.font.display, fontSize: 28, fontWeight: 800, letterSpacing: '-0.025em', marginBottom: 20 }}>{Math.round(totalHours * 10) / 10}h tracked</div>
          {/* Stacked bar */}
          <div style={{ display: 'flex', height: 8, borderRadius: 4, overflow: 'hidden', marginBottom: 14 }}>
            {cats.map(c => {
              const ec = ChronoTokens.events[c.color];
              return <div key={c.id} style={{ flex: c.count / totalForBar, background: ec.glow }}/>;
            })}
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {cats.map(c => {
              const ec = ChronoTokens.events[c.color];
              return (
                <div key={c.id} style={{ display: 'flex', alignItems: 'center', gap: 10, fontSize: 12.5 }}>
                  <span style={{ width: 8, height: 8, borderRadius: 2, background: ec.glow }}/>
                  <span style={{ flex: 1, color: theme.textDim }}>{c.name}</span>
                  <span style={{ fontFamily: ChronoTokens.font.mono, color: theme.textFaint, fontSize: 11 }}>{c.hours}h</span>
                </div>
              );
            })}
          </div>
        </div>
      </div>
    </div>
  );
}

// ── Trips — AI vacation itinerary builder ────────────────────────────────
// Enter a destination + dates; the assistant builds a timed, buffered
// day-by-day plan and drops every item onto the main calendar under a
// "Travel" category. Transit buffers between activities keep the user on time.
const TRIP_MAX_DAYS = 10;

// All events on the calendar belonging to a given trip (tagged with `trip`),
// grouped by day-offset and sorted by start. Read live from the store.
function tripDaysFor(destination) {
  const byOff = new Map();
  Object.keys(EVENTS_BY_OFFSET).forEach(k => {
    const off = parseInt(k, 10);
    EVENTS_BY_OFFSET[k].forEach(e => {
      if (e.trip && e.trip === destination) {
        if (!byOff.has(off)) byOff.set(off, []);
        byOff.get(off).push(e);
      }
    });
  });
  return [...byOff.keys()].sort((a, b) => a - b).map(off => ({
    off,
    date: dateAtOffset(off),
    events: byOff.get(off).slice().sort((a, b) => tm(a.start) - tm(b.start)),
  }));
}

// Group a flat list of draft events (each carrying an `off`) into the same
// day-by-day shape tripDaysFor() returns, so the draft and the committed trip
// render through one itinerary view.
function groupEventsByOff(events) {
  const byOff = new Map();
  (events || []).forEach(e => {
    if (!byOff.has(e.off)) byOff.set(e.off, []);
    byOff.get(e.off).push(e);
  });
  return [...byOff.keys()].sort((a, b) => a - b).map(off => ({
    off,
    date: dateAtOffset(off),
    events: byOff.get(off).slice().sort((a, b) => tm(a.start) - tm(b.start)),
  }));
}

// Map the model's returned events into draft rows, clamped to the trip window.
function mapTripEvents(final, startOff, endOff) {
  const out = [];
  if (final && Array.isArray(final.events)) {
    final.events.forEach(ev => {
      if (!ev || typeof ev !== 'object') return;
      const off = offsetFromDayLabel(ev.day || ev.date);
      if (off < startOff - 1 || off > endOff + 1) return; // keep within the trip window
      const color = (ev.color && ChronoTokens.events[ev.color]) ? ev.color : 'grey';
      out.push({
        title: String(ev.title || 'Activity').slice(0, 120),
        start: ev.start || '09:00',
        end: ev.end || '10:00',
        color,
        note: String(ev.note || '').slice(0, 280),
        off,
      });
    });
  }
  return out;
}

const TRIP_PACE_TEXT = {
  relaxed: 'relaxed — 2–3 main activities per day, leisurely meals, built-in downtime',
  packed: 'packed — early starts and as much as can comfortably fit without rushing',
  balanced: 'balanced — 3–4 activities per day with breathing room between them',
};

// A date picker styled to match Chrono (instead of the OS-native one). Opens a
// themed month grid that highlights TODAY (accent ring) and the selected day
// (filled accent), respects the user's "Week starts on" setting, and can
// enforce a minimum date (used so departure can't precede arrival).
function ThemedDatePicker({ theme, value, onChange, min, disabled }) {
  const settings = useSettings();
  const weekStartsOn = settings.weekStartsOn || 0;
  const [open, setOpen] = React.useState(false);
  const wrapRef = React.useRef(null);

  const selected = value ? ymdToDate(value) : null;
  const [viewMonth, setViewMonth] = React.useState(() => {
    const base = selected || new Date(CHRONO_TODAY);
    return new Date(base.getFullYear(), base.getMonth(), 1);
  });
  // Re-center the grid on the selected month if the value changes externally.
  React.useEffect(() => {
    if (value) { const d = ymdToDate(value); setViewMonth(new Date(d.getFullYear(), d.getMonth(), 1)); }
  }, [value]);

  // Close on outside-click or Escape.
  React.useEffect(() => {
    if (!open) return;
    const onDown = e => { if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false); };
    const onKey = e => { if (e.key === 'Escape') setOpen(false); };
    window.addEventListener('mousedown', onDown);
    window.addEventListener('keydown', onKey);
    return () => { window.removeEventListener('mousedown', onDown); window.removeEventListener('keydown', onKey); };
  }, [open]);

  const today = new Date(CHRONO_TODAY); today.setHours(0,0,0,0);
  const minDate = min ? ymdToDate(min) : null; if (minDate) minDate.setHours(0,0,0,0);
  const sameDay = (a, b) => a && b && a.getFullYear()===b.getFullYear() && a.getMonth()===b.getMonth() && a.getDate()===b.getDate();

  const first = new Date(viewMonth.getFullYear(), viewMonth.getMonth(), 1);
  const lead = (first.getDay() - weekStartsOn + 7) % 7;
  const gridStart = new Date(first); gridStart.setDate(1 - lead);
  const cells = Array.from({ length: 42 }, (_, i) => { const d = new Date(gridStart); d.setDate(gridStart.getDate()+i); return d; });
  const dowLabels = Array.from({ length: 7 }, (_, i) => DAY_NAMES[(i + weekStartsOn) % 7]);

  const label = selected
    ? `${MONTH_NAMES[selected.getMonth()].slice(0,3)} ${selected.getDate()}, ${selected.getFullYear()}`
    : 'Select date';

  const pick = (d) => {
    const dd = new Date(d); dd.setHours(0,0,0,0);
    if (minDate && dd < minDate) return;
    onChange(dateToYMD(d));
    setOpen(false);
  };

  return (
    <div ref={wrapRef} style={{ position: 'relative' }}>
      <button type="button" disabled={disabled} onClick={() => !disabled && setOpen(o => !o)} style={{
        width: '100%', appearance: 'none', cursor: disabled ? 'default' : 'pointer',
        display: 'flex', alignItems: 'center', gap: 8,
        border: `0.5px solid ${open ? theme.accentColor + '88' : theme.hairline}`,
        background: theme.isDark ? 'rgba(255,255,255,0.04)' : 'rgba(10,12,20,0.03)',
        color: theme.text, fontFamily: ChronoTokens.font.ui, fontSize: 13, fontWeight: 500,
        padding: '9px 11px', borderRadius: 9, outline: 'none', boxSizing: 'border-box',
        opacity: disabled ? 0.6 : 1,
      }}>
        <span style={{ display: 'inline-flex', color: theme.textDim }}><Icon.cal s={13}/></span>
        <span style={{ flex: 1, textAlign: 'left' }}>{label}</span>
        <span style={{ display: 'inline-flex', color: theme.textFaint, transform: 'rotate(90deg)' }}><Icon.fwd s={10}/></span>
      </button>

      {open && (
        <div style={{
          position: 'absolute', top: 'calc(100% + 6px)', left: 0, zIndex: 80,
          width: 268, padding: 12, borderRadius: 12,
          background: theme.isDark ? '#171a24' : '#ffffff',
          border: `1px solid ${theme.accentColor}44`,
          boxShadow: theme.isDark ? '0 18px 44px rgba(0,0,0,0.6)' : '0 18px 44px rgba(10,12,20,0.2)',
          animation: 'chrono-backdrop-in 120ms ease both',
        }}>
          {/* Month nav */}
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
            <button type="button" onClick={()=>setViewMonth(m=>new Date(m.getFullYear(), m.getMonth()-1, 1))} style={navBtnStyle(theme)}><Icon.back s={13}/></button>
            <div style={{ fontFamily: ChronoTokens.font.display, fontSize: 13.5, fontWeight: 700, color: theme.text }}>
              {MONTH_NAMES[viewMonth.getMonth()]} {viewMonth.getFullYear()}
            </div>
            <button type="button" onClick={()=>setViewMonth(m=>new Date(m.getFullYear(), m.getMonth()+1, 1))} style={navBtnStyle(theme)}><Icon.fwd s={13}/></button>
          </div>
          {/* Weekday header */}
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', marginBottom: 4 }}>
            {dowLabels.map((d, i) => (
              <div key={i} style={{
                textAlign: 'center', fontFamily: ChronoTokens.font.mono, fontSize: 9, fontWeight: 700,
                color: theme.textFaint, letterSpacing: '0.06em', textTransform: 'uppercase', padding: '2px 0',
              }}>{d.slice(0,1)}</div>
            ))}
          </div>
          {/* Day grid */}
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 2 }}>
            {cells.map((d, i) => {
              const inMonth = d.getMonth() === viewMonth.getMonth();
              const isToday = sameDay(d, today);
              const isSelected = sameDay(d, selected);
              const dd = new Date(d); dd.setHours(0,0,0,0);
              const isDisabled = minDate && dd < minDate;
              return (
                <button key={i} type="button" disabled={isDisabled} onClick={()=>pick(d)} style={{
                  appearance: 'none', cursor: isDisabled ? 'default' : 'pointer',
                  height: 30, borderRadius: 8,
                  border: isToday && !isSelected ? `1px solid ${theme.accentColor}` : '1px solid transparent',
                  background: isSelected
                    ? `linear-gradient(135deg, ${theme.accentColor}, ${theme.accentColor}CC)`
                    : 'transparent',
                  color: isSelected ? '#fff'
                    : isToday ? theme.accentColor
                    : inMonth ? theme.text : theme.textFaint,
                  fontFamily: ChronoTokens.font.ui, fontSize: 12.5,
                  fontWeight: isSelected || isToday ? 700 : 500,
                  opacity: isDisabled ? 0.3 : (inMonth ? 1 : 0.45),
                  boxShadow: isSelected ? theme.accentGlow : 'none',
                  transition: 'background 120ms ease',
                }}>{d.getDate()}</button>
              );
            })}
          </div>
          {/* Quick "Today" jump */}
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 8, paddingTop: 8, borderTop: `0.5px solid ${theme.hairline}` }}>
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontFamily: ChronoTokens.font.mono, fontSize: 9.5, color: theme.textFaint }}>
              <span style={{ width: 9, height: 9, borderRadius: 3, border: `1px solid ${theme.accentColor}` }}/> Today
            </span>
            <button type="button" onClick={()=>{ setViewMonth(new Date(today.getFullYear(), today.getMonth(), 1)); }} style={{
              appearance: 'none', cursor: 'pointer', border: 'none', background: 'transparent',
              color: theme.accentColor, fontFamily: ChronoTokens.font.ui, fontSize: 11.5, fontWeight: 600, padding: 0,
            }}>Jump to today</button>
          </div>
        </div>
      )}
    </div>
  );
}
function navBtnStyle(theme) {
  return {
    appearance: 'none', cursor: 'pointer',
    width: 26, height: 26, borderRadius: 7,
    border: `0.5px solid ${theme.hairline}`,
    background: theme.isDark ? 'rgba(255,255,255,0.04)' : 'rgba(10,12,20,0.03)',
    color: theme.textDim, display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
  };
}

function DesktopTrips({ theme, personality, onGoToWeek }) {
  useEvents();
  const settings = useSettings();
  const today = new Date(CHRONO_TODAY);
  const defStart = new Date(today); defStart.setDate(defStart.getDate() + 14);
  const defEnd   = new Date(today); defEnd.setDate(defEnd.getDate() + 18);

  const [destination, setDestination] = React.useState('');
  const [startDate, setStartDate] = React.useState(dateToYMD(defStart));
  const [endDate, setEndDate]     = React.useState(dateToYMD(defEnd));
  const [pace, setPace] = React.useState('balanced');
  const [objectives, setObjectives] = React.useState('');
  const [busy, setBusy] = React.useState(false);     // planning OR refining
  const [busyLabel, setBusyLabel] = React.useState('Planning');
  const [error, setError] = React.useState('');
  const [draft, setDraft] = React.useState(null);    // { destination, startOff, endOff, dates, events:[] }
  const [refineText, setRefineText] = React.useState('');
  const [activeTrip, setActiveTrip] = React.useState(null); // destination once committed
  const abortRef = React.useRef(null);
  React.useEffect(() => () => { if (abortRef.current) abortRef.current.abort(); }, []);

  // Shared AI context. The clarification style + vagueness preference are fed
  // through so the planner respects the user's Assistant settings.
  const aiContext = () => ({
    timeFormat: settings.timeFormat,
    followups: settings.aiFollowups,
    bufferMin: settings.aiBufferMin,
    categories: CATEGORIES.map(c => ({ id: c.id, name: c.name, color: c.color })),
  });

  // ── Build the first draft (does NOT touch the calendar) ─────────────────
  const plan = async () => {
    const dest = destination.trim();
    if (!dest) { setError('Where are you going? Add a destination.'); return; }
    if (busy) return;
    const s = ymdToDate(startDate), e = ymdToDate(endDate);
    s.setHours(0,0,0,0); e.setHours(0,0,0,0);
    if (e < s) { setError('Your end date is before your start date.'); return; }
    const dayCount = Math.round((e - s) / 86400000) + 1;
    if (dayCount > TRIP_MAX_DAYS) { setError(`Keep trips to ${TRIP_MAX_DAYS} days or fewer for now.`); return; }
    if (typeof window.streamAssistant !== 'function') {
      setError('AI client not loaded — make sure the assistant proxy is running on http://localhost:3001.');
      return;
    }
    setError(''); setBusy(true); setBusyLabel('Drafting');

    const startOff = offsetForDate(s), endOff = offsetForDate(e);
    const msg =
      `Draft a complete, day-by-day vacation itinerary for a trip to ${dest}.\n` +
      `Dates: ${startDate} to ${endDate} inclusive (${dayCount} day${dayCount > 1 ? 's' : ''}).\n` +
      `Pace: ${TRIP_PACE_TEXT[pace]}.\n` +
      (objectives.trim() ? `What the traveler wants to do / their goals: ${objectives.trim()}.\n` : '') +
      `Requirements:\n` +
      `- Emit one <event> per item, using the exact ISO date for each day, covering EVERY day in the range.\n` +
      `- Build the plan around the traveler's stated goals above — prioritize those.\n` +
      `- Day 1 includes arrival / hotel check-in; the last day includes check-out / departure.\n` +
      `- Include breakfast, lunch and dinner each day at realistic local spots.\n` +
      `- Between activities in different areas, add a SHORT transit block titled "Travel to <place>" with color grey so I leave on time.\n` +
      `- Use realistic opening hours and durations; never overlap two events.\n` +
      `- Put a one-line note on each event for what to do / why.\n` +
      `- This is a DRAFT for review — do not assume it is final.`;

    const snap = snapshotEventsForAI();
    const eventsForServer = snap.map(({ _ref, ...rest }) => rest);

    abortRef.current = new AbortController();
    await window.streamAssistant({
      messages: [{ role: 'user', content: msg }],
      personality, taskHint: 'complex',
      events: eventsForServer, userContext: aiContext(),
      signal: abortRef.current.signal,
      onDone: (final) => {
        const evs = mapTripEvents(final, startOff, endOff);
        setBusy(false);
        if (evs.length > 0) {
          setDraft({ destination: dest, startOff, endOff, dates: `${startDate} → ${endDate}`, events: evs });
          setRefineText('');
        } else {
          setError('The planner didn’t return any activities — try again, or adjust the dates.');
        }
      },
      onError: (er) => { setBusy(false); setError(er?.message || 'Planning failed. Is the proxy running?'); },
    });
  };

  // ── Refine the existing draft via a natural-language change request ──────
  const refine = async (instruction) => {
    const change = (instruction || '').trim();
    if (!draft || !change || busy) return;
    setError(''); setBusy(true); setBusyLabel('Updating');

    // Serialize the current draft so the model edits THIS plan rather than
    // starting over, then ask it to return the complete revised itinerary.
    const draftLines = groupEventsByOff(draft.events).map(({ date, events }) => {
      const head = `${dateToYMD(date)} (${DAY_FULL[date.getDay()]}):`;
      const rows = events.map(ev => `  - ${ev.start}–${ev.end} ${ev.title}${ev.note ? ' — ' + ev.note : ''}`).join('\n');
      return `${head}\n${rows}`;
    }).join('\n');

    const msg =
      `Here is the CURRENT DRAFT itinerary for ${draft.destination} (${draft.dates}):\n${draftLines}\n\n` +
      `The traveler wants this change: "${change}".\n` +
      `Apply it and return the COMPLETE updated itinerary — every activity for every day, not just the changed ones — as <event> blocks with exact ISO dates. ` +
      `Keep travel buffers, realistic times, and no overlaps. Honor the requested change even if it means adding, removing, retiming, or replacing events.`;

    const snap = snapshotEventsForAI();
    const eventsForServer = snap.map(({ _ref, ...rest }) => rest);

    abortRef.current = new AbortController();
    await window.streamAssistant({
      messages: [{ role: 'user', content: msg }],
      personality, taskHint: 'complex',
      events: eventsForServer, userContext: aiContext(),
      signal: abortRef.current.signal,
      onDone: (final) => {
        const evs = mapTripEvents(final, draft.startOff, draft.endOff);
        setBusy(false);
        if (evs.length > 0) { setDraft(d => ({ ...d, events: evs })); setRefineText(''); }
        else setError('Couldn’t apply that change — try rephrasing it.');
      },
      onError: (er) => { setBusy(false); setError(er?.message || 'Update failed. Is the proxy running?'); },
    });
  };

  // ── Commit the approved draft to the calendar ───────────────────────────
  const acceptDraft = () => {
    if (!draft || busy) return;
    const dest = draft.destination;
    // Replace any earlier committed plan for this destination.
    tripDaysFor(dest).forEach(d => d.events.forEach(ev => deleteEventByRef(ev)));
    const travelCat = addCategory({ name: 'Travel', color: 'pink' });
    draft.events.forEach(ev => {
      const color = (ev.color && ChronoTokens.events[ev.color]) ? ev.color : travelCat.color;
      addEventAtOffset(ev.off, {
        title: ev.title, start: ev.start, end: ev.end,
        color, category: travelCat.id, note: ev.note || '', trip: dest,
      });
    });
    setActiveTrip(dest);
    setDraft(null);
  };

  const discardDraft = () => { if (!busy) { setDraft(null); setError(''); } };

  const cancelBusy = () => { if (abortRef.current) abortRef.current.abort(); setBusy(false); };

  const clearTrip = () => {
    if (!activeTrip) return;
    tripDaysFor(activeTrip).forEach(d => d.events.forEach(ev => deleteEventByRef(ev)));
    setActiveTrip(null);
  };

  const committedDays = activeTrip ? tripDaysFor(activeTrip) : [];
  const committedTotal = committedDays.reduce((s, d) => s + d.events.length, 0);
  const draftDays = draft ? groupEventsByOff(draft.events) : [];
  const draftTotal = draft ? draft.events.length : 0;

  const inputStyle = {
    width: '100%', appearance: 'none', border: `0.5px solid ${theme.hairline}`,
    background: theme.isDark ? 'rgba(255,255,255,0.04)' : 'rgba(10,12,20,0.03)',
    color: theme.text, fontFamily: ChronoTokens.font.ui, fontSize: 13,
    padding: '9px 11px', borderRadius: 9, outline: 'none', boxSizing: 'border-box',
    colorScheme: theme.isDark ? 'dark' : 'light',
  };
  const labelStyle = {
    fontFamily: ChronoTokens.font.mono, fontSize: 10, fontWeight: 700,
    color: theme.textFaint, letterSpacing: '0.12em', textTransform: 'uppercase',
    marginBottom: 6, display: 'block',
  };
  const tripStartWeekOff = activeTrip && committedDays.length
    ? committedDays[0].off - dateAtOffset(committedDays[0].off).getDay()
    : 0;

  // Reusable day-by-day itinerary renderer (draft + committed share it).
  const renderDays = (days) => (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
      {days.map(({ off, date, events }) => (
        <div key={off} style={{
          borderRadius: 12, overflow: 'hidden',
          border: `0.5px solid ${theme.hairline}`,
          background: theme.isDark ? 'rgba(20,22,32,0.5)' : 'rgba(255,255,255,0.7)',
        }}>
          <div style={{
            padding: '10px 14px', borderBottom: `0.5px solid ${theme.hairline}`,
            display: 'flex', alignItems: 'center', gap: 8,
            background: theme.isDark ? 'rgba(255,255,255,0.02)' : 'rgba(10,12,20,0.02)',
          }}>
            <span style={{ fontFamily: ChronoTokens.font.display, fontSize: 14, fontWeight: 700 }}>
              {DAY_FULL[date.getDay()]}
            </span>
            <span style={{ fontFamily: ChronoTokens.font.mono, fontSize: 11, color: theme.textFaint }}>
              {MONTH_NAMES[date.getMonth()].slice(0,3)} {date.getDate()}
            </span>
          </div>
          <div style={{ display: 'flex', flexDirection: 'column' }}>
            {events.map((ev, i) => {
              const c = ChronoTokens.events[ev.color] || ChronoTokens.events.grey;
              return (
                <div key={i} style={{
                  display: 'flex', alignItems: 'flex-start', gap: 12, padding: '9px 14px',
                  borderTop: i === 0 ? 'none' : `0.5px solid ${theme.hairline}`,
                }}>
                  <span style={{
                    flex: 'none', width: 92, paddingTop: 1,
                    fontFamily: ChronoTokens.font.mono, fontSize: 11, color: theme.textDim, fontWeight: 600,
                  }}>{fmtTime(ev.start)}</span>
                  <span style={{
                    flex: 'none', width: 8, height: 8, borderRadius: 3, marginTop: 5,
                    background: `linear-gradient(135deg, ${c.base}, ${c.glow})`,
                  }}/>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontFamily: ChronoTokens.font.ui, fontSize: 13, fontWeight: 600, color: theme.text }}>{ev.title}</div>
                    {ev.note && <div style={{ fontFamily: ChronoTokens.font.ui, fontSize: 12, color: theme.textDim, marginTop: 1, lineHeight: 1.4 }}>{ev.note}</div>}
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      ))}
    </div>
  );

  const REFINE_EXAMPLES = [
    'Add a free morning on day 2',
    'Swap one dinner for a food market',
    'Make it less packed',
    'Add more museums',
  ];

  return (
    <div style={{ position: 'absolute', inset: 0, overflow: 'auto', padding: 32 }} className="chrono-scrollbar">
      <DesktopHeader theme={theme} eyebrow="Plan ahead, stay on time" title="Trips"/>

      <div style={{ marginTop: 20, maxWidth: 880, display: 'flex', flexDirection: 'column', gap: 20 }}>
        {/* Planner form */}
        <div style={{
          padding: 22, borderRadius: 16,
          background: theme.isDark
            ? `linear-gradient(135deg, ${theme.accentColor}12, transparent 55%), rgba(20,22,32,0.55)`
            : `linear-gradient(135deg, ${theme.accentColor}0E, transparent 55%), rgba(255,255,255,0.75)`,
          border: `0.5px solid ${theme.hairline}`,
        }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 16 }}>
            <div style={{
              width: 34, height: 34, borderRadius: 10,
              background: `radial-gradient(circle at 30% 30%, ${theme.accentColor}, ${theme.accentColor}66)`,
              boxShadow: theme.accentGlow, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: '#fff',
            }}><Icon.pin s={16}/></div>
            <div>
              <div style={{ fontFamily: ChronoTokens.font.display, fontSize: 17, fontWeight: 700, letterSpacing: '-0.01em' }}>Plan a trip</div>
              <div style={{ fontFamily: ChronoTokens.font.ui, fontSize: 12, color: theme.textDim }}>
                The assistant drafts a timed itinerary from your goals — review and tweak it before it touches your calendar.
              </div>
            </div>
          </div>

          <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
            <div>
              <label style={labelStyle}>Destination</label>
              <input value={destination} onChange={e=>setDestination(e.target.value)}
                placeholder="e.g. Kyoto, Japan" maxLength={80} disabled={busy}
                onKeyDown={e => { if (e.key === 'Enter') plan(); }}
                style={inputStyle}/>
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
              <div>
                <label style={labelStyle}>Arrival</label>
                <ThemedDatePicker theme={theme} value={startDate} disabled={busy}
                  onChange={ymd => {
                    setStartDate(ymd);
                    // Keep departure on/after arrival.
                    if (ymdToDate(endDate) < ymdToDate(ymd)) setEndDate(ymd);
                  }}/>
              </div>
              <div>
                <label style={labelStyle}>Departure</label>
                <ThemedDatePicker theme={theme} value={endDate} min={startDate} disabled={busy}
                  onChange={setEndDate}/>
              </div>
            </div>
            <div>
              <label style={labelStyle}>Pace</label>
              <ComposerSegmented theme={theme} value={pace} onChange={setPace} options={[
                { v: 'relaxed', label: 'Relaxed' },
                { v: 'balanced', label: 'Balanced' },
                { v: 'packed', label: 'Packed' },
              ]}/>
            </div>
            <div>
              <label style={labelStyle}>What do you want to do?</label>
              <textarea value={objectives} onChange={e=>setObjectives(e.target.value)}
                placeholder="Tell the AI your goals — e.g. street food, temples and gardens, one hot-spring day, an easy first morning, no early starts…"
                maxLength={600} disabled={busy} rows={3}
                style={{ ...inputStyle, resize: 'vertical', minHeight: 64, lineHeight: 1.5 }}/>
            </div>

            {error && <div style={{
              fontFamily: ChronoTokens.font.ui, fontSize: 12.5, color: ChronoTokens.events.red.base,
            }}>{error}</div>}

            <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginTop: 2 }}>
              <DeskBtn theme={theme} primary onClick={plan}>
                {busy ? <><Spinner theme={theme}/> {busyLabel}…</> : <><Icon.sparkle s={13}/> {draft || activeTrip ? 'Re-draft trip' : 'Draft my trip'}</>}
              </DeskBtn>
              {busy && (
                <button className="chrono-tap" onClick={cancelBusy} style={{
                  appearance: 'none', cursor: 'pointer', border: `0.5px solid ${theme.hairline}`,
                  background: 'transparent', color: theme.textDim, borderRadius: 8,
                  padding: '6px 12px', fontFamily: ChronoTokens.font.ui, fontSize: 12, fontWeight: 600,
                }}>Cancel</button>
              )}
              <div style={{ flex: 1 }}/>
              <span style={{ fontFamily: ChronoTokens.font.mono, fontSize: 10.5, color: theme.textFaint }}>
                gpt-4o · up to {TRIP_MAX_DAYS} days
              </span>
            </div>
          </div>
        </div>

        {/* Draft itinerary — review, refine, then accept or discard */}
        {draft && draftDays.length > 0 && (
          <div>
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, marginBottom: 6, flexWrap: 'wrap' }}>
              <span style={{
                fontFamily: ChronoTokens.font.mono, fontSize: 10, fontWeight: 700, letterSpacing: '0.14em',
                textTransform: 'uppercase', color: theme.accentColor,
                padding: '3px 9px', borderRadius: 999, border: `0.5px solid ${theme.accentColor}55`, background: `${theme.accentColor}14`,
              }}>First draft</span>
              <div style={{ fontFamily: ChronoTokens.font.display, fontSize: 19, fontWeight: 700, letterSpacing: '-0.015em' }}>
                {draft.destination}
              </div>
              <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 11.5, color: theme.textDim }}>
                {draftDays.length} day{draftDays.length > 1 ? 's' : ''} · {draftTotal} activities · not yet on your calendar
              </div>
            </div>
            <div style={{ fontFamily: ChronoTokens.font.ui, fontSize: 12.5, color: theme.textDim, marginBottom: 14 }}>
              Looks good? Add it to your calendar. Want changes? Tell the assistant what to add, remove, or rework.
            </div>

            {/* Refine box */}
            <div style={{
              padding: 14, borderRadius: 12, marginBottom: 16,
              border: `0.5px solid ${theme.accentColor}33`,
              background: theme.isDark ? `${theme.accentColor}0C` : `${theme.accentColor}08`,
            }}>
              <div style={{ display: 'flex', gap: 8, alignItems: 'flex-start' }}>
                <input value={refineText} onChange={e=>setRefineText(e.target.value)}
                  placeholder="e.g. add a cooking class on day 2 and drop the late dinners"
                  maxLength={300} disabled={busy}
                  onKeyDown={e => { if (e.key === 'Enter') refine(refineText); }}
                  style={{ ...inputStyle, flex: 1 }}/>
                <DeskBtn theme={theme} primary onClick={()=>refine(refineText)}>
                  {busy ? <><Spinner theme={theme}/> {busyLabel}…</> : <><Icon.sparkle s={12}/> Apply</>}
                </DeskBtn>
              </div>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 10 }}>
                {REFINE_EXAMPLES.map(ex => (
                  <button key={ex} className="chrono-tap" disabled={busy} onClick={()=>refine(ex)} style={{
                    appearance: 'none', cursor: busy ? 'default' : 'pointer',
                    padding: '4px 10px', borderRadius: 999, border: `0.5px solid ${theme.hairline}`,
                    background: theme.isDark ? 'rgba(255,255,255,0.04)' : 'rgba(10,12,20,0.03)',
                    color: theme.textDim, fontFamily: ChronoTokens.font.ui, fontSize: 11.5, fontWeight: 600,
                    opacity: busy ? 0.5 : 1,
                  }}>{ex}</button>
                ))}
              </div>
            </div>

            {renderDays(draftDays)}

            {/* Accept / discard */}
            <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 16 }}>
              <DeskBtn theme={theme} primary onClick={acceptDraft}>
                <Icon.cal s={13}/> Add to calendar
              </DeskBtn>
              <button className="chrono-tap" onClick={discardDraft} disabled={busy} style={{
                appearance: 'none', cursor: busy ? 'default' : 'pointer', border: `0.5px solid ${theme.hairline}`,
                background: 'transparent', color: theme.textDim, borderRadius: 8,
                padding: '7px 14px', fontFamily: ChronoTokens.font.ui, fontSize: 12.5, fontWeight: 600,
                opacity: busy ? 0.5 : 1,
              }}>Discard draft</button>
            </div>
          </div>
        )}

        {/* Committed itinerary (after the user accepts a draft) */}
        {!draft && activeTrip && committedDays.length > 0 && (
          <div>
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, marginBottom: 12, flexWrap: 'wrap' }}>
              <span style={{
                fontFamily: ChronoTokens.font.mono, fontSize: 10, fontWeight: 700, letterSpacing: '0.14em',
                textTransform: 'uppercase', color: '#52d094',
                padding: '3px 9px', borderRadius: 999, border: '0.5px solid #52d09455', background: 'rgba(82,208,148,0.12)',
              }}>On calendar</span>
              <div style={{ fontFamily: ChronoTokens.font.display, fontSize: 19, fontWeight: 700, letterSpacing: '-0.015em' }}>
                {activeTrip}
              </div>
              <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 11.5, color: theme.textDim }}>
                {committedDays.length} day{committedDays.length > 1 ? 's' : ''} · {committedTotal} activities
              </div>
              <div style={{ flex: 1 }}/>
              <DeskBtn theme={theme} onClick={()=>onGoToWeek && onGoToWeek(tripStartWeekOff)}><Icon.cal s={12}/> View on calendar</DeskBtn>
              <button className="chrono-tap" onClick={clearTrip} style={{
                appearance: 'none', cursor: 'pointer', border: `0.5px solid ${ChronoTokens.events.red.base}44`,
                background: 'transparent', color: ChronoTokens.events.red.base, borderRadius: 8,
                padding: '6px 12px', fontFamily: ChronoTokens.font.ui, fontSize: 12, fontWeight: 600,
                display: 'inline-flex', alignItems: 'center', gap: 6,
              }}><Icon.trash s={12}/> Clear trip</button>
            </div>
            {renderDays(committedDays)}
          </div>
        )}
      </div>
    </div>
  );
}

function Spinner({ theme }) {
  return (
    <span style={{
      display: 'inline-block', width: 12, height: 12, borderRadius: 999,
      border: `2px solid ${theme.isDark ? 'rgba(255,255,255,0.35)' : 'rgba(255,255,255,0.6)'}`,
      borderTopColor: '#fff', animation: 'chrono-rotate 0.7s linear infinite',
      verticalAlign: 'middle',
    }}/>
  );
}

// ── Canvas LMS ───────────────────────────────────────────────────────────
function DesktopCanvas({ theme }) {
  const integrations = useIntegrations();
  const [connecting, setConnecting] = React.useState(false);

  if (!integrations.canvas) {
    return (
      <div style={{ position: 'absolute', inset: 0, overflow: 'auto', padding: 32 }} className="chrono-scrollbar">
        <DesktopHeader theme={theme} eyebrow="Not connected" title="Canvas LMS"
          right={<DeskBtn theme={theme} primary onClick={()=>setConnecting(true)}><Icon.link s={12}/> Connect Canvas</DeskBtn>}/>
        <div style={{
          marginTop: 24, maxWidth: 1100,
          display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
          textAlign: 'center', gap: 16, padding: '80px 24px',
          border: `0.5px dashed ${theme.hairline}`, borderRadius: 16,
          background: theme.isDark ? 'rgba(20,22,32,0.4)' : 'rgba(255,255,255,0.5)',
        }}>
          <div style={{
            width: 64, height: 64, borderRadius: 16,
            background: `linear-gradient(135deg, ${ChronoTokens.events.yellow.base}, ${ChronoTokens.events.yellow.glow})`,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            color: '#5A4500', fontWeight: 800, fontFamily: ChronoTokens.font.display, fontSize: 26,
            boxShadow: `0 0 30px ${ChronoTokens.events.yellow.glow}44`,
          }}>C</div>
          <div style={{ fontFamily: ChronoTokens.font.display, fontSize: 24, fontWeight: 800, letterSpacing: '-0.025em' }}>Connect your school</div>
          <div style={{ color: theme.textDim, fontSize: 14, lineHeight: 1.55, maxWidth: 380 }}>
            Link Canvas LMS to import courses, due dates, and exams — Chrono will build study blocks around them automatically.
          </div>
          <DeskBtn theme={theme} primary onClick={()=>setConnecting(true)} style={{ marginTop: 4 }}><Icon.link s={12}/> Connect Canvas</DeskBtn>
        </div>
        {connecting && <CanvasConnectModal theme={theme} onClose={()=>setConnecting(false)}/>}
      </div>
    );
  }

  // Connected: show course list + upcoming assignments.
  const allAssignments = integrations.canvasCourses
    .flatMap(c => c.assignments.map(a => ({ ...a, courseCode: c.code, courseColor: c.color })))
    .filter(a => a.dueOff >= 0)
    .sort((a,b) => a.dueOff - b.dueOff);

  return (
    <div style={{ position: 'absolute', inset: 0, overflow: 'auto', padding: 32 }} className="chrono-scrollbar">
      <DesktopHeader theme={theme} eyebrow={`Connected · ${integrations.canvasSchool}`} title="Canvas LMS"
        right={
          <DeskBtn theme={theme} onClick={()=>{ clearCanvasEvents(); setIntegrations({ canvas: false, canvasSchool: '', canvasCourses: [] }); }}>
            Disconnect
          </DeskBtn>
        }/>
      <div style={{
        marginTop: 24, maxWidth: 1100,
        display: 'grid', gridTemplateColumns: '1.3fr 1fr', gap: 24,
      }}>
        {/* Courses */}
        <div>
          <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 10.5, fontWeight: 700, color: theme.textFaint, letterSpacing: '0.18em', textTransform: 'uppercase', marginBottom: 12 }}>
            Courses · {integrations.canvasCourses.length}
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {integrations.canvasCourses.map(c => {
              const ec = ChronoTokens.events[c.color];
              const upcoming = c.assignments.filter(a => a.dueOff >= 0).length;
              return (
                <div key={c.id} style={{
                  display: 'flex', alignItems: 'center', gap: 14, padding: 16, borderRadius: 12,
                  background: theme.isDark ? 'rgba(20,22,32,0.5)' : 'rgba(255,255,255,0.7)',
                  border: `0.5px solid ${theme.hairline}`,
                }}>
                  <div style={{
                    width: 40, height: 40, borderRadius: 10,
                    background: `linear-gradient(135deg, ${ec.base}, ${ec.glow})`,
                    boxShadow: `0 0 16px ${ec.glow}55`,
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                    color: '#fff', fontFamily: ChronoTokens.font.mono, fontSize: 11, fontWeight: 700,
                  }}>{c.code.slice(0,3)}</div>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontFamily: ChronoTokens.font.display, fontSize: 15, fontWeight: 700, letterSpacing: '-0.01em' }}>{c.name}</div>
                    <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 11, color: theme.textFaint, letterSpacing: '0.04em', marginTop: 2 }}>
                      {c.code} · {c.assignments.length} total · {upcoming} upcoming
                    </div>
                  </div>
                </div>
              );
            })}
          </div>
        </div>
        {/* Upcoming assignments */}
        <div>
          <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 10.5, fontWeight: 700, color: theme.textFaint, letterSpacing: '0.18em', textTransform: 'uppercase', marginBottom: 12 }}>
            Upcoming · {allAssignments.length}
          </div>
          <div style={{
            padding: 8, borderRadius: 12,
            background: theme.isDark ? 'rgba(20,22,32,0.5)' : 'rgba(255,255,255,0.7)',
            border: `0.5px solid ${theme.hairline}`,
            display: 'flex', flexDirection: 'column', gap: 2,
          }}>
            {allAssignments.length === 0 ? (
              <div style={{ padding: 18, textAlign: 'center', color: theme.textDim, fontSize: 13 }}>
                Nothing due — you're clear.
              </div>
            ) : allAssignments.slice(0, 8).map((a, i) => {
              const c = ChronoTokens.events[a.courseColor];
              const d = dateAtOffset(a.dueOff);
              const dayLabel = a.dueOff === 0 ? 'Today'
                             : a.dueOff === 1 ? 'Tomorrow'
                             : `${DAY_NAMES[d.getDay()]} · ${MONTH_NAMES[d.getMonth()].slice(0,3)} ${d.getDate()}`;
              return (
                <div key={i} style={{
                  display: 'flex', alignItems: 'center', gap: 10, padding: '8px 10px', borderRadius: 8,
                }}>
                  <div style={{
                    width: 3, alignSelf: 'stretch', minHeight: 28, borderRadius: 2,
                    background: `linear-gradient(180deg, ${c.base}, ${c.glow})`,
                  }}/>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontFamily: ChronoTokens.font.ui, fontSize: 13, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                      {a.title}
                    </div>
                    <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 10.5, color: theme.textDim, marginTop: 2 }}>
                      {a.courseCode} · {dayLabel}
                    </div>
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      </div>
    </div>
  );
}

function CanvasConnectModal({ theme, onClose }) {
  const [school, setSchool] = React.useState('');
  const confirm = () => {
    const s = school.trim();
    if (!s) return;
    const courses = mockCanvasCourses();
    setIntegrations({ canvas: true, canvasSchool: s, canvasCourses: courses });
    setCanvasEvents(courses); // surface assignment due dates on the calendar
    onClose();
  };
  return (
    <div onClick={onClose} style={{
      position: 'absolute', inset: 0, zIndex: 70,
      background: 'rgba(0,0,0,0.55)', backdropFilter: 'blur(12px)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      animation: 'chrono-backdrop-in 200ms ease both',
    }}>
      <div onClick={e=>e.stopPropagation()} style={{
        width: 420, padding: 24, borderRadius: 14,
        background: theme.isDark ? 'rgba(20,22,32,0.96)' : 'rgba(255,255,255,0.98)',
        border: `0.5px solid ${theme.hairline}`,
        boxShadow: '0 30px 80px rgba(0,0,0,0.5)',
        animation: `chrono-modal-in 240ms ${Spring.std} both`,
      }}>
        <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 10, fontWeight: 700, color: theme.accentColor, letterSpacing: '0.18em', textTransform: 'uppercase', marginBottom: 8 }}>Connect</div>
        <div style={{ fontFamily: ChronoTokens.font.display, fontSize: 22, fontWeight: 800, letterSpacing: '-0.02em', marginBottom: 6 }}>Connect Canvas LMS</div>
        <div style={{ color: theme.textDim, fontSize: 13, lineHeight: 1.5, marginBottom: 16 }}>
          Enter your school's Canvas subdomain (e.g. <span style={{ color: theme.text, fontFamily: ChronoTokens.font.mono }}>uiuc</span>). Chrono will import your courses and assignments.
        </div>
        <input autoFocus value={school} onChange={e=>setSchool(e.target.value)}
          onKeyDown={e=>{ if (e.key==='Enter') confirm(); if (e.key==='Escape') onClose(); }}
          placeholder="your-school"
          style={{
            width: '100%', appearance: 'none', outline: 'none',
            padding: '10px 12px', borderRadius: 10,
            background: theme.isDark ? 'rgba(0,0,0,0.35)' : 'rgba(10,12,20,0.04)',
            border: `0.5px solid ${theme.hairline}`,
            color: theme.text, fontFamily: ChronoTokens.font.ui, fontSize: 14, fontWeight: 500,
            marginBottom: 16,
          }}/>
        <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
          <DeskBtn theme={theme} onClick={onClose}>Cancel</DeskBtn>
          <DeskBtn theme={theme} primary onClick={confirm}><Icon.link s={12}/> Connect</DeskBtn>
        </div>
      </div>
    </div>
  );
}

function mockCanvasCourses() {
  return [
    { id: 'cs374', code: 'CS 374', name: 'Algorithms & Models of Computation', color: 'purple', assignments: [
      { title: 'Problem Set 5',      dueOff: 3,  weight: 'PS' },
      { title: 'Midterm 2',          dueOff: 10, weight: 'Exam' },
      { title: 'Problem Set 6',      dueOff: 17, weight: 'PS' },
    ]},
    { id: 'math415', code: 'MATH 415', name: 'Applied Linear Algebra', color: 'yellow', assignments: [
      { title: 'Homework 7',         dueOff: 1,  weight: 'HW' },
      { title: 'Quiz 3',             dueOff: 6,  weight: 'Quiz' },
      { title: 'Homework 8',         dueOff: 14, weight: 'HW' },
    ]},
    { id: 'ece391', code: 'ECE 391', name: 'Computer Systems Engineering', color: 'red', assignments: [
      { title: 'MP3 Checkpoint',     dueOff: 5,  weight: 'MP' },
      { title: 'Final report',       dueOff: 22, weight: 'Project' },
    ]},
    { id: 'engl215', code: 'ENGL 215', name: 'British Literature', color: 'green', assignments: [
      { title: 'Reading response 4', dueOff: 2,  weight: 'Essay' },
      { title: 'Term paper draft',   dueOff: 18, weight: 'Paper' },
    ]},
  ];
}

// ── Profile ──────────────────────────────────────────────────────────────
function DesktopProfile({ theme, onUpgrade }) {
  useEvents();
  const profile = useProfile();
  const integrations = useIntegrations();
  const auth = (window.chronoAuth && window.chronoAuth.use) ? window.chronoAuth.use() : null;
  const stats = eventStats();
  const [editing, setEditing] = React.useState(null);  // 'name' | 'email' | null
  const [draftName, setDraftName]   = React.useState(profile.name);
  const [draftEmail, setDraftEmail] = React.useState(profile.email);
  const [connectingCanvas, setConnectingCanvas] = React.useState(false);
  const [feedbackOpen, setFeedbackOpen] = React.useState(false);

  const startEdit = (field) => {
    setDraftName(profile.name);
    setDraftEmail(profile.email);
    setEditing(field);
  };
  const saveEdit = () => {
    if (editing === 'name')  setProfile({ name:  draftName.trim()  || 'Your name' });
    if (editing === 'email') setProfile({ email: draftEmail.trim() || 'you@chrono.app' });
    setEditing(null);
  };

  const isPro = true; // Beta: every tester gets Pro. Tiers return after beta.

  const integrationList = [
    { key: 'canvas',  label: 'Canvas LMS' },
    { key: 'google',  label: 'Google Calendar', comingSoon: true },
    { key: 'outlook', label: 'Outlook 365',     comingSoon: true },
    { key: 'icloud',  label: 'Apple iCloud',    comingSoon: true },
  ];
  const disconnect = (k) => {
    if (k === 'canvas') { clearCanvasEvents(); setIntegrations({ canvas: false, canvasSchool: '', canvasCourses: [] }); }
    else setIntegrations({ [k]: false });
  };

  const editInputStyle = {
    appearance: 'none', border: `0.5px solid ${theme.accentColor}66`,
    background: theme.isDark ? 'rgba(0,0,0,0.3)' : 'rgba(255,255,255,0.9)',
    color: theme.text, outline: 'none',
    padding: '4px 8px', borderRadius: 6,
  };

  return (
    <div style={{ position: 'absolute', inset: 0, overflow: 'auto', padding: 32 }} className="chrono-scrollbar">
      <DesktopHeader theme={theme} eyebrow="Account" title="Your profile"/>
      <div style={{ marginTop: 24, display: 'grid', gridTemplateColumns: '1.4fr 1fr', gap: 24, maxWidth: 1100 }}>
        {/* Identity card */}
        <div style={{
          padding: 28, borderRadius: 16,
          background: theme.isDark
            ? `linear-gradient(135deg, ${theme.accentColor}1A, transparent 70%), rgba(20,22,32,0.6)`
            : `linear-gradient(135deg, ${theme.accentColor}14, transparent 70%), rgba(255,255,255,0.7)`,
          border: `0.5px solid ${theme.hairline}`,
        }}>
          <div style={{ display: 'flex', gap: 18, alignItems: 'center', marginBottom: 22 }}>
            <GradientBlob seed={profile.name || 'you'} size={72}/>
            <div style={{ flex: 1, minWidth: 0 }}>
              {editing === 'name' ? (
                <input autoFocus value={draftName} onChange={e=>setDraftName(e.target.value)}
                  onKeyDown={e=>{ if (e.key==='Enter') saveEdit(); if (e.key==='Escape') setEditing(null); }}
                  onBlur={saveEdit}
                  style={{ ...editInputStyle,
                    fontFamily: ChronoTokens.font.display, fontSize: 26, fontWeight: 800,
                    letterSpacing: '-0.025em', width: '100%',
                  }}/>
              ) : (
                <div onClick={()=>startEdit('name')} title="Click to edit"
                  style={{ fontFamily: ChronoTokens.font.display, fontSize: 26, fontWeight: 800, letterSpacing: '-0.025em', cursor: 'pointer' }}>
                  {profile.name}
                </div>
              )}
              {editing === 'email' ? (
                <input autoFocus type="email" value={draftEmail} onChange={e=>setDraftEmail(e.target.value)}
                  onKeyDown={e=>{ if (e.key==='Enter') saveEdit(); if (e.key==='Escape') setEditing(null); }}
                  onBlur={saveEdit}
                  style={{ ...editInputStyle,
                    fontFamily: ChronoTokens.font.mono, fontSize: 11, marginTop: 4, width: '100%',
                  }}/>
              ) : (
                <div onClick={()=>startEdit('email')} title="Click to edit"
                  style={{ fontFamily: ChronoTokens.font.mono, fontSize: 11, color: theme.textDim, letterSpacing: '0.04em', marginTop: 2, cursor: 'pointer' }}>
                  {profile.email}
                </div>
              )}
              <div style={{ display: 'flex', gap: 8, marginTop: 8, alignItems: 'center' }}>
                <span style={{
                  fontFamily: ChronoTokens.font.mono, fontSize: 9.5, fontWeight: 700,
                  padding: '3px 8px', borderRadius: 4,
                  background: `linear-gradient(135deg, ${theme.accentColor}, ${theme.signal || theme.accentColor})`,
                  color: '#fff', letterSpacing: '0.16em', textTransform: 'uppercase',
                  boxShadow: theme.accentGlow,
                }}>Pro</span>
                <span style={{
                  fontFamily: ChronoTokens.font.mono, fontSize: 9.5, fontWeight: 700,
                  padding: '3px 8px', borderRadius: 4,
                  border: `0.5px solid ${theme.accentColor}55`, color: theme.accentColor,
                  letterSpacing: '0.16em', textTransform: 'uppercase',
                }}>Beta</span>
              </div>
            </div>
          </div>
          {/* Account & sync — sign in to persist across devices */}
          {auth && (
            <div style={{
              display: 'flex', alignItems: 'center', gap: 12, marginBottom: 20,
              padding: '12px 14px', borderRadius: 10,
              background: theme.isDark ? 'rgba(0,0,0,0.25)' : 'rgba(10,12,20,0.03)',
              border: `0.5px solid ${theme.hairline}`,
            }}>
              <span style={{
                width: 8, height: 8, borderRadius: 999,
                background: auth.isAuthed() ? '#52d094' : theme.textFaint,
                boxShadow: auth.isAuthed() ? '0 0 6px #52d094' : 'none',
              }}/>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 12.5, fontWeight: 600 }}>
                  {auth.isAuthed()
                    ? (auth.getStatus() === 'syncing' ? 'Syncing…' : auth.getStatus() === 'error' ? 'Sync error — will retry' : 'Synced to your account')
                    : 'Not signed in — changes stay on this device'}
                </div>
                <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 10, color: theme.textFaint, marginTop: 2 }}>
                  {auth.isAuthed() ? (auth.getUser() && auth.getUser().email) : 'Sign in to sync events, categories & settings'}
                </div>
              </div>
              <button onClick={()=>{ if (auth.isAuthed()) auth.logout(); else { try { localStorage.removeItem('chrono.auth.skip.v1'); } catch(e){} location.reload(); } }}
                className="chrono-tap" style={{
                  appearance: 'none', cursor: 'pointer',
                  border: `0.5px solid ${theme.hairline}`, color: theme.text,
                  background: theme.isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)',
                  padding: '7px 13px', borderRadius: 8,
                  fontFamily: ChronoTokens.font.ui, fontSize: 12, fontWeight: 700,
                }}>{auth.isAuthed() ? 'Sign out' : 'Sign in'}</button>
            </div>
          )}
          {/* Stats — computed from real events */}
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 12 }}>
            {[
              { l: 'Hours scheduled', v: `${stats.totalHours}h`, s: 'across all events' },
              { l: 'Events tracked',  v: `${stats.totalEvents}`, s: 'lifetime' },
              { l: 'Integrations',    v: `${integrationList.filter(i=>integrations[i.key]).length}/${integrationList.length}`, s: 'connected' },
            ].map((s, i) => (
              <div key={i} style={{
                padding: 14, borderRadius: 10,
                background: theme.isDark ? 'rgba(0,0,0,0.25)' : 'rgba(10,12,20,0.03)',
                border: `0.5px solid ${theme.hairline}`,
              }}>
                <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 9.5, fontWeight: 700, color: theme.textFaint, letterSpacing: '0.16em', textTransform: 'uppercase', marginBottom: 6 }}>{s.l}</div>
                <div style={{ fontFamily: ChronoTokens.font.display, fontSize: 22, fontWeight: 800, letterSpacing: '-0.02em' }}>{s.v}</div>
                <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 10, color: theme.textFaint, marginTop: 2 }}>{s.s}</div>
              </div>
            ))}
          </div>
        </div>
        {/* Beta card — Pro is unlocked for all testers; the CTA collects feedback */}
        <div style={{
          padding: 22, borderRadius: 16,
          background: `linear-gradient(135deg, ${theme.accentColor}, ${theme.signal || theme.accentColor})`,
          color: '#fff', position: 'relative', overflow: 'hidden',
        }}>
          <div style={{ position: 'absolute', top: -40, right: -40, width: 160, height: 160, borderRadius: '50%', background: 'rgba(255,255,255,0.15)' }}/>
          <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 10, fontWeight: 700, letterSpacing: '0.2em', opacity: 0.85, marginBottom: 10, textTransform: 'uppercase' }}>Private beta · Pro unlocked</div>
          <div style={{ fontFamily: ChronoTokens.font.display, fontSize: 22, fontWeight: 800, letterSpacing: '-0.02em', marginBottom: 8 }}>
            Thanks for testing Chrono
          </div>
          <div style={{ fontSize: 13, opacity: 0.92, lineHeight: 1.5, marginBottom: 16 }}>
            Every feature is unlocked while we test. Found a bug or have an idea? Send it our way — it goes straight to the team.
          </div>
          <div style={{ display: 'flex', gap: 8 }}>
            <button onClick={()=>setFeedbackOpen(true)} className="chrono-tap chrono-btn" style={{
              appearance: 'none', cursor: 'pointer', border: 'none',
              display: 'inline-flex', alignItems: 'center', gap: 7,
              padding: '8px 14px', borderRadius: 8,
              background: 'rgba(255,255,255,0.18)', color: '#fff',
              fontFamily: ChronoTokens.font.ui, fontSize: 12.5, fontWeight: 700,
              backdropFilter: 'blur(10px)',
            }}><Icon.send s={13}/> Send feedback</button>
          </div>
        </div>
        {/* Devices */}
        <div style={{
          padding: 22, borderRadius: 14,
          background: theme.isDark ? 'rgba(20,22,32,0.5)' : 'rgba(255,255,255,0.7)',
          border: `0.5px solid ${theme.hairline}`,
        }}>
          <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 10.5, fontWeight: 700, color: theme.textFaint, letterSpacing: '0.18em', textTransform: 'uppercase', marginBottom: 14 }}>Devices · 1 of {isPro ? 5 : 2}</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
              <span style={{ width: 8, height: 8, borderRadius: 999, background: '#52d094', boxShadow: '0 0 6px #52d094' }}/>
              <span style={{ flex: 1, fontSize: 13, fontWeight: 500 }}>This device</span>
              <span style={{ fontFamily: ChronoTokens.font.mono, fontSize: 11, color: theme.textFaint }}>now</span>
            </div>
          </div>
        </div>
        {/* Integrations */}
        <div style={{
          padding: 22, borderRadius: 14,
          background: theme.isDark ? 'rgba(20,22,32,0.5)' : 'rgba(255,255,255,0.7)',
          border: `0.5px solid ${theme.hairline}`,
        }}>
          <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 10.5, fontWeight: 700, color: theme.textFaint, letterSpacing: '0.18em', textTransform: 'uppercase', marginBottom: 14 }}>Integrations</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {integrationList.map(({ key, label, comingSoon }) => {
              const on = !!integrations[key];
              const dotColor = comingSoon ? theme.textFaint : (on ? '#52d094' : theme.textFaint);
              return (
                <div key={key} style={{ display: 'flex', alignItems: 'center', gap: 10, opacity: comingSoon ? 0.6 : 1 }}>
                  <span style={{
                    width: 8, height: 8, borderRadius: on ? 999 : 2,
                    background: dotColor,
                    boxShadow: on && !comingSoon ? '0 0 6px #52d094' : 'none',
                  }}/>
                  <span style={{ flex: 1, fontSize: 13, fontWeight: 500, color: on ? theme.text : theme.textDim }}>{label}</span>
                  {comingSoon ? (
                    <span style={{
                      fontFamily: ChronoTokens.font.mono, fontSize: 10.5, fontWeight: 700,
                      color: theme.textFaint, letterSpacing: '0.04em',
                      padding: '3px 9px', borderRadius: 999, border: `0.5px solid ${theme.hairline}`,
                    }}>Coming soon</span>
                  ) : on ? (
                    <button onClick={()=>disconnect(key)} style={{
                      appearance: 'none', cursor: 'pointer', border: 'none',
                      background: 'transparent', color: theme.textFaint,
                      fontFamily: ChronoTokens.font.mono, fontSize: 11, fontWeight: 600,
                      padding: 0,
                    }}>Disconnect</button>
                  ) : (
                    <button onClick={()=>setConnectingCanvas(true)} style={{
                      appearance: 'none', cursor: 'pointer', border: 'none',
                      background: 'transparent', color: theme.accentColor,
                      fontFamily: ChronoTokens.font.mono, fontSize: 11, fontWeight: 700,
                      padding: 0,
                    }}>Connect</button>
                  )}
                </div>
              );
            })}
          </div>
        </div>
      </div>
      {connectingCanvas && <CanvasConnectModal theme={theme} onClose={()=>setConnectingCanvas(false)}/>}
      {feedbackOpen && <FeedbackModal theme={theme} defaultEmail={profile.email} onClose={()=>setFeedbackOpen(false)}/>}
    </div>
  );
}

// ── Beta feedback (modal) ─────────────────────────────────────────────────
// Lets beta testers file a bug or feature idea. Posts to the proxy's
// /api/feedback endpoint, which stores it server-side for the team.
function FeedbackModal({ theme, defaultEmail, onClose }) {
  const [kind, setKind] = React.useState('bug'); // 'bug' | 'feature'
  const [message, setMessage] = React.useState('');
  const [email, setEmail] = React.useState(defaultEmail && defaultEmail !== 'you@chrono.app' ? defaultEmail : '');
  const [status, setStatus] = React.useState(null); // null | 'sending' | 'sent' | 'error'
  const [errMsg, setErrMsg] = React.useState('');

  const submit = async () => {
    const text = message.trim();
    if (!text) { setErrMsg('Please describe the bug or idea first.'); setStatus('error'); return; }
    setStatus('sending'); setErrMsg('');
    try {
      const res = await window.submitFeedback({
        type: kind, message: text, email: email.trim(),
        meta: { path: 'profile', ua: navigator.userAgent.slice(0, 200) },
      });
      if (res && res.ok) { setStatus('sent'); setTimeout(onClose, 1100); }
      else { setStatus('error'); setErrMsg((res && res.error) || 'Could not send. Try again.'); }
    } catch (e) {
      setStatus('error'); setErrMsg('Could not reach the server. Is it running?');
    }
  };

  const field = {
    width: '100%', appearance: 'none', boxSizing: 'border-box',
    border: `0.5px solid ${theme.hairline}`,
    background: theme.isDark ? 'rgba(0,0,0,0.3)' : 'rgba(10,12,20,0.03)',
    color: theme.text, outline: 'none', borderRadius: 9,
    padding: '9px 11px', fontFamily: ChronoTokens.font.ui, fontSize: 13,
  };

  return (
    <div onClick={onClose} style={{
      position: 'absolute', inset: 0, zIndex: 80,
      background: 'rgba(0,0,0,0.55)', backdropFilter: 'blur(12px)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      animation: 'chrono-backdrop-in 200ms ease both',
    }}>
      <div onClick={e=>e.stopPropagation()} style={{
        width: 460, maxWidth: '92%', padding: 24, borderRadius: 14,
        background: theme.isDark ? 'rgba(20,22,32,0.97)' : 'rgba(255,255,255,0.99)',
        border: `0.5px solid ${theme.hairline}`,
        boxShadow: '0 30px 80px rgba(0,0,0,0.5)',
        animation: `chrono-modal-in 240ms ${Spring.std} both`,
      }}>
        <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 10, fontWeight: 700, color: theme.accentColor, letterSpacing: '0.18em', textTransform: 'uppercase', marginBottom: 8 }}>Beta feedback</div>
        <div style={{ fontFamily: ChronoTokens.font.display, fontSize: 21, fontWeight: 800, letterSpacing: '-0.02em', marginBottom: 14 }}>Tell us what to fix or build</div>

        {status === 'sent' ? (
          <div style={{ padding: '24px 0', textAlign: 'center' }}>
            <div style={{ fontSize: 30, marginBottom: 8 }}>✓</div>
            <div style={{ fontFamily: ChronoTokens.font.ui, fontSize: 14, color: theme.text }}>Thanks — sent to the team.</div>
          </div>
        ) : (
          <>
            <ComposerSegmented theme={theme} value={kind} onChange={setKind} options={[
              { v: 'bug', label: '🐞 Bug' },
              { v: 'feature', label: '✨ Feature idea' },
            ]}/>
            <textarea autoFocus value={message} onChange={e=>setMessage(e.target.value)} maxLength={4000} rows={5}
              placeholder={kind === 'bug'
                ? 'What happened, and what did you expect? Steps to reproduce help a lot.'
                : 'What would you like Chrono to do?'}
              style={{ ...field, resize: 'vertical', minHeight: 110, lineHeight: 1.5, marginTop: 12 }}/>
            <input value={email} onChange={e=>setEmail(e.target.value)} maxLength={120}
              placeholder="Email (optional — so we can follow up)"
              style={{ ...field, marginTop: 10 }}/>
            {status === 'error' && errMsg && (
              <div style={{ marginTop: 10, fontFamily: ChronoTokens.font.ui, fontSize: 12.5, color: ChronoTokens.events.red.base }}>{errMsg}</div>
            )}
            <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 16 }}>
              <DeskBtn theme={theme} onClick={onClose}>Cancel</DeskBtn>
              <DeskBtn theme={theme} primary onClick={submit}>
                {status === 'sending' ? <><Spinner theme={theme}/> Sending…</> : <><Icon.send s={12}/> Send</>}
              </DeskBtn>
            </div>
          </>
        )}
      </div>
    </div>
  );
}

// ── Paywall (modal) ──────────────────────────────────────────────────────
function DesktopPaywall({ theme, onClose }) {
  const [billing, setBilling] = React.useState('annual');
  return (
    <div onClick={onClose} style={{
      position: 'absolute', inset: 0, zIndex: 60,
      background: 'rgba(0,0,0,0.55)', backdropFilter: 'blur(14px)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      animation: 'chrono-backdrop-in 220ms ease both',
    }}>
      <div onClick={e=>e.stopPropagation()} style={{
        width: 700, maxHeight: '92%', overflow: 'auto',
        borderRadius: 18, padding: 36,
        background: theme.isDark ? 'rgba(20,22,32,0.96)' : 'rgba(255,255,255,0.98)',
        border: `0.5px solid ${theme.hairline}`,
        boxShadow: theme.isDark
          ? `0 30px 80px rgba(0,0,0,0.6), 0 0 60px ${theme.accentColor}33`
          : `0 30px 80px rgba(10,12,20,0.18)`,
        animation: `chrono-modal-in 320ms ${Spring.std} both`,
        position: 'relative',
      }} className="chrono-scrollbar">
        <button onClick={onClose} className="chrono-tap chrono-btn" style={{
          position: 'absolute', top: 16, right: 16,
          appearance: 'none', cursor: 'pointer', border: 'none',
          width: 28, height: 28, borderRadius: 8,
          background: theme.isDark ? 'rgba(255,255,255,0.06)' : 'rgba(10,12,20,0.05)',
          color: theme.textDim, display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
        }}><Icon.close s={14}/></button>
        <div style={{ textAlign: 'center', marginBottom: 22 }}>
          <div style={{ fontFamily: ChronoTokens.font.mono, fontSize: 11, fontWeight: 700, color: theme.accentColor, letterSpacing: '0.2em', textTransform: 'uppercase', marginBottom: 12 }}>Upgrade</div>
          <div style={{ fontFamily: ChronoTokens.font.display, fontSize: 32, fontWeight: 800, letterSpacing: '-0.03em', lineHeight: 1.1, marginBottom: 8 }}>Get Chrono Pro</div>
          <div style={{ color: theme.textDim, fontSize: 14, lineHeight: 1.5, maxWidth: 460, margin: '0 auto' }}>
            Unlock Concierge AI, Canvas + Outlook sync, unlimited categories, and priority support.
          </div>
        </div>
        {/* Billing */}
        <div style={{ display: 'flex', justifyContent: 'center', marginBottom: 24 }}>
          <div style={{
            display: 'inline-flex', padding: 4, borderRadius: 999,
            background: theme.isDark ? 'rgba(0,0,0,0.3)' : 'rgba(10,12,20,0.05)',
            border: `0.5px solid ${theme.hairline}`,
          }}>
            {[{id:'monthly',label:'Monthly · $12'}, {id:'annual',label:'Annual · $9 · save 25%'}].map(o => (
              <button key={o.id} className="chrono-tap" onClick={()=>setBilling(o.id)} style={{
                appearance: 'none', cursor: 'pointer', border: 'none',
                padding: '8px 16px', borderRadius: 999,
                background: billing === o.id
                  ? `linear-gradient(135deg, ${theme.accentColor}, ${theme.signal || theme.accentColor})`
                  : 'transparent',
                color: billing === o.id ? '#fff' : theme.textDim,
                fontSize: 12.5, fontWeight: 600,
                boxShadow: billing === o.id ? `0 0 18px ${theme.accentColor}66` : 'none',
              }}>{o.label}</button>
            ))}
          </div>
        </div>
        {/* Features */}
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginBottom: 26 }}>
          {[
            'Concierge AI · all personalities',
            'Auto-blocked focus time',
            'Canvas LMS sync',
            'Outlook 365 + Exchange',
            'Unlimited categories',
            'Up to 5 devices',
            'End-to-end encrypted sync',
            'Priority support · <2h replies',
          ].map((f,i) => (
            <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '6px 0' }}>
              <span style={{ width: 18, height: 18, borderRadius: 999, background: `${theme.accentColor}22`, color: theme.accentColor, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 10, fontWeight: 700, flexShrink: 0 }}>✓</span>
              <span style={{ fontSize: 13.5, color: theme.textDim }}>{f}</span>
            </div>
          ))}
        </div>
        <button className="chrono-tap chrono-btn" style={{
          width: '100%', appearance: 'none', cursor: 'pointer', border: 'none',
          padding: '14px 22px', borderRadius: 12,
          background: `linear-gradient(135deg, ${theme.accentColor}, ${theme.signal || theme.accentColor})`,
          color: '#fff', fontSize: 15, fontWeight: 700, letterSpacing: '-0.005em',
          boxShadow: `0 0 30px ${theme.accentColor}66`,
          display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 10,
        }}>
          <Icon.sparkle s={14}/>
          Start 14-day free trial
        </button>
        <div style={{ textAlign: 'center', marginTop: 12, fontFamily: ChronoTokens.font.mono, fontSize: 10.5, color: theme.textFaint, letterSpacing: '0.06em' }}>
          Cancel anytime. We'll remind you 3 days before billing starts.
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { ChronoDesktop, mockCanvasCourses });
