/* Calmer view modes — Agenda, Focus, Cards
   For users who get overwhelmed by dense lists / boards. */

/* ─────────── Agenda ─────────── */
function AgendaView({ tasks, onSelect, selectedId, onStatusChange }) {
  // Group tasks by day relative to today. Buckets: overdue, today, tomorrow, this week (Wed–Sun), later, undated.
  const buckets = React.useMemo(() => {
    const todayStr = TODAY.toISOString().slice(0, 10);
    const groups = [
      { key: 'overdue',     title: 'Overdue',           sub: 'catch up first',           tasks: [], tint: 'rgb(var(--brick))',  bg: 'rgb(var(--brick-soft))' },
      { key: 'today',       title: 'Today',             sub: TODAY.toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long' }), tasks: [], tint: 'rgb(var(--accent))', bg: 'rgb(var(--accent-soft))' },
      { key: 'tomorrow',    title: 'Tomorrow',          sub: null,                       tasks: [], tint: 'rgb(var(--warm))',   bg: 'rgb(var(--warm-soft))' },
      { key: 'this-week',   title: 'Later this week',   sub: null,                       tasks: [], tint: 'rgb(var(--forest))', bg: 'rgb(var(--forest-soft))' },
      { key: 'next-week',   title: 'Next week onwards', sub: null,                       tasks: [], tint: 'rgb(var(--navy))',   bg: 'rgb(var(--navy-soft))' },
      { key: 'someday',     title: 'Someday',           sub: 'no date set',              tasks: [], tint: 'rgb(var(--muted))',  bg: 'rgb(var(--paper-2))' },
    ];
    tasks.forEach(t => {
      if (!t.dueDate) { groups[5].tasks.push(t); return; }
      const delta = dayDelta(t.dueDate);
      if (delta < 0) groups[0].tasks.push(t);
      else if (delta === 0) groups[1].tasks.push(t);
      else if (delta === 1) groups[2].tasks.push(t);
      else if (delta <= 7)  groups[3].tasks.push(t);
      else                  groups[4].tasks.push(t);
    });
    // sort within each bucket
    groups.forEach(g => g.tasks.sort((a, b) => {
      const dueA = a.dueDate || '9999';
      const dueB = b.dueDate || '9999';
      if (dueA !== dueB) return dueA.localeCompare(dueB);
      const rank = { urgent: 0, high: 1, medium: 2, low: 3 };
      return rank[a.priority] - rank[b.priority];
    }));
    return groups.filter(g => g.tasks.length > 0);
  }, [tasks]);

  return (
    <div style={{ flex: 1, overflowY: 'auto', background: 'rgb(var(--bg))' }}>
      <div style={{ maxWidth: 760, margin: '0 auto', padding: '24px 28px 80px' }}>

        <div style={{ textAlign: 'center', marginBottom: 28 }}>
          <p style={{ margin: 0, fontSize: 12, color: 'rgb(var(--muted))', letterSpacing: '0.04em', textTransform: 'uppercase', fontWeight: 600 }}>Agenda</p>
          <h1 style={{ margin: '4px 0 0', fontSize: 28, fontWeight: 700, letterSpacing: '-0.025em' }}>One step at a time.</h1>
          <p style={{ margin: '6px 0 0', fontSize: 14, color: 'rgb(var(--muted))' }}>{tasks.length} task{tasks.length === 1 ? '' : 's'} across {buckets.length} time bucket{buckets.length === 1 ? '' : 's'}.</p>
        </div>

        {buckets.map((b, bi) => (
          <section key={b.key} style={{ marginBottom: 36 }}>
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 12, marginBottom: 14 }}>
              <span style={{
                width: 10, height: 10, background: b.tint, borderRadius: '50%',
                alignSelf: 'center',
              }} />
              <div>
                <h2 style={{ margin: 0, fontSize: 18, fontWeight: 700, letterSpacing: '-0.02em', color: 'rgb(var(--ink))' }}>{b.title}</h2>
                {b.sub && <p style={{ margin: '2px 0 0', fontSize: 12.5, color: 'rgb(var(--muted))' }}>{b.sub}</p>}
              </div>
              <span style={{ flex: 1, height: 1, background: 'rgb(var(--rule))', marginLeft: 4 }} />
              <span style={{
                fontSize: 11, fontWeight: 600, padding: '3px 10px',
                background: b.bg, color: b.tint, borderRadius: 999,
              }}>{b.tasks.length}</span>
            </div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
              {b.tasks.map(t => <AgendaRow key={t.id} task={t} selected={t.id === selectedId} onClick={() => onSelect(t.id)} onComplete={() => onStatusChange?.(t.id, 6)} />)}
            </div>
          </section>
        ))}

        {buckets.length === 0 && (
          <div style={{ marginTop: 80, textAlign: 'center', padding: 40, color: 'rgb(var(--muted))' }}>
            <div style={{ fontSize: 32, marginBottom: 10 }}>🍵</div>
            <h3 style={{ margin: 0, fontSize: 18, fontWeight: 700, color: 'rgb(var(--ink))' }}>Nothing to do right now.</h3>
            <p style={{ margin: '6px 0 0', fontSize: 13.5 }}>Take a breath. The list will fill itself soon enough.</p>
          </div>
        )}

      </div>
    </div>
  );
}

function AgendaRow({ task, selected, onClick, onComplete }) {
  const sp = spaceById(task.spaceId);
  const done = task.statusId === 6;
  return (
    <div onClick={onClick} onDoubleClick={(e) => { e.stopPropagation(); window.__openTaskModal?.(task.id); }} data-link style={{
      display: 'flex', alignItems: 'center', gap: 14,
      padding: '14px 18px',
      background: selected ? 'rgb(var(--accent-soft) / 0.4)' : 'rgb(var(--surface))',
      border: '1px solid ' + (selected ? 'rgb(var(--accent) / 0.4)' : 'rgb(var(--rule))'),
      borderRadius: 12,
      boxShadow: selected ? 'none' : 'var(--sh-sm)',
      cursor: 'pointer',
      transition: 'background-color .14s, box-shadow .14s, transform .06s',
    }}
      onMouseEnter={e => { if (!selected) e.currentTarget.style.boxShadow = 'var(--sh-md)'; }}
      onMouseLeave={e => { if (!selected) e.currentTarget.style.boxShadow = 'var(--sh-sm)'; }}
    >
      <button onClick={e => { e.stopPropagation(); onComplete(); }} style={{
        width: 22, height: 22, borderRadius: '50%',
        border: '1.5px solid ' + (done ? 'rgb(var(--forest))' : 'rgb(var(--rule-2))'),
        background: done ? 'rgb(var(--forest))' : 'transparent',
        flexShrink: 0,
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
        color: 'white', fontSize: 12, fontWeight: 700,
        transition: 'all .14s',
      }}
        onMouseEnter={e => { if (!done) { e.currentTarget.style.borderColor = 'rgb(var(--forest))'; e.currentTarget.style.background = 'rgb(var(--forest-soft))'; }}}
        onMouseLeave={e => { if (!done) { e.currentTarget.style.borderColor = 'rgb(var(--rule-2))'; e.currentTarget.style.background = 'transparent'; }}}
      >{done && '✓'}</button>

      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
          <PriorityDot p={task.priority} />
          <h3 style={{
            margin: 0, fontSize: 15, fontWeight: 600, letterSpacing: '-0.01em',
            color: done ? 'rgb(var(--muted))' : 'rgb(var(--ink))',
            textDecoration: done ? 'line-through' : 'none',
            overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
          }}>{task.title}</h3>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, fontSize: 12, color: 'rgb(var(--muted))' }}>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
            <span style={{ width: 6, height: 6, background: sp.colour, borderRadius: 2 }} />
            {sp.name}
          </span>
          {task.dueDate && <><span>·</span><DueChip iso={task.dueDate} /></>}
          {task.estimateMins > 0 && <><span>·</span><span style={{ fontFamily: 'JetBrains Mono, monospace' }}>{fmtMins(task.estimateMins)}</span></>}
        </div>
      </div>

      <div style={{ flexShrink: 0 }}>
        {task.assignees.length > 0 ? <AvatarStack ids={task.assignees} max={2} size={22} /> : null}
      </div>
    </div>
  );
}

/* ─────────── Focus (one-at-a-time) ─────────── */
function FocusView({ tasks, onSelect, selectedId, onStatusChange, onStartFocus }) {
  const [loading, setLoading] = React.useState(true);

  const ordered = React.useMemo(() => {
    const rank = { urgent: 0, high: 1, medium: 2, low: 3 };
    return [...tasks].sort((a, b) => {
      if (a.statusId === 6 && b.statusId !== 6) return 1;
      if (b.statusId === 6 && a.statusId !== 6) return -1;
      const dueA = a.dueDate || '9999';
      const dueB = b.dueDate || '9999';
      if (dueA !== dueB) return dueA.localeCompare(dueB);
      return rank[a.priority] - rank[b.priority];
    });
  }, [tasks]);

  const initialIdx = Math.max(0, ordered.findIndex(t => t.id === selectedId));
  const [idx, setIdx] = React.useState(initialIdx === -1 ? 0 : initialIdx);

  React.useEffect(() => {
    setLoading(true);
    const t = setTimeout(() => setLoading(false), 450);
    return () => clearTimeout(t);
  }, []);
  React.useEffect(() => { if (!loading && ordered[idx]) onSelect?.(ordered[idx].id); }, [idx, ordered, loading]);
  React.useEffect(() => {
    if (loading) return;
    const i = ordered.findIndex(t => t.id === selectedId);
    if (i !== -1 && i !== idx) setIdx(i);
  }, [selectedId, ordered, loading]);

  const goPrev = React.useCallback(() => setIdx(i => Math.max(0, i - 1)), []);
  const goNext = React.useCallback(() => setIdx(i => Math.min(ordered.length - 1, i + 1)), [ordered.length]);
  const complete = () => { const t = ordered[idx]; if (t) onStatusChange?.(t.id, 6); setTimeout(goNext, 200); };

  React.useEffect(() => {
    const onKey = (e) => {
      const tag = (e.target.tagName || '').toLowerCase();
      if (tag === 'input' || tag === 'textarea' || e.target.isContentEditable) return;
      if (e.key === 'ArrowLeft')  { e.preventDefault(); goPrev(); }
      if (e.key === 'ArrowRight') { e.preventDefault(); goNext(); }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [goPrev, goNext]);

  if (loading) {
    return (
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', background: 'linear-gradient(180deg, rgb(var(--bg)) 0%, rgb(var(--accent-soft) / 0.3) 100%)' }}>
        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 16 }}>
          <div style={{
            width: 56, height: 56, borderRadius: 16,
            background: 'linear-gradient(135deg, rgb(var(--accent)), rgb(var(--berry)))',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            color: 'white', fontSize: 22, fontWeight: 700,
            animation: 'pulse-dot 1.4s infinite',
          }}>◉</div>
          <div style={{ display: 'flex', gap: 6 }}>
            {[0,1,2].map(i => (
              <span key={i} style={{
                width: 8, height: 8, borderRadius: '50%',
                background: 'rgb(var(--accent))',
                animation: `pulse-dot 1.2s ${i*0.18}s infinite`,
              }} />
            ))}
          </div>
          <p style={{ margin: 0, fontSize: 13, color: 'rgb(var(--muted))', fontWeight: 500 }}>Preparing your focus queue…</p>
        </div>
      </div>
    );
  }


  if (ordered.length === 0) {
    return (
      <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'rgb(var(--bg))' }}>
        <div style={{ textAlign: 'center', padding: 60 }}>
          <div style={{ fontSize: 40, marginBottom: 10 }}>🌿</div>
          <h2 style={{ margin: 0, fontSize: 22, fontWeight: 700 }}>You're done.</h2>
          <p style={{ margin: '8px 0 0', color: 'rgb(var(--muted))', fontSize: 14 }}>No tasks match your filters. Time for a tea?</p>
        </div>
      </div>
    );
  }

  const t = ordered[idx];
  const sp = spaceById(t.spaceId);
  const subs = (t.subtasks || []).length + (t.checklist || []).length;
  const done = (t.subtasks || []).filter(s => s.done).length + (t.checklist || []).filter(c => c.done).length;
  const isDone = t.statusId === 6;

  return (
    <div style={{
      flex: 1, display: 'flex', flexDirection: 'column',
      background: 'linear-gradient(180deg, rgb(var(--bg)) 0%, rgb(var(--accent-soft) / 0.3) 100%)',
      overflow: 'hidden',
    }}>
      {/* Progress strip */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '14px 32px' }}>
        <span style={{ fontFamily: 'JetBrains Mono, monospace', fontSize: 12, color: 'rgb(var(--muted))', fontWeight: 600 }}>
          {String(idx + 1).padStart(2, '0')} <span style={{ opacity: 0.5 }}>/ {String(ordered.length).padStart(2, '0')}</span>
        </span>
        <div style={{ flex: 1, height: 4, background: 'rgb(var(--paper-2))', borderRadius: 999, overflow: 'hidden' }}>
          <div style={{
            height: '100%',
            width: `${((idx + 1) / ordered.length) * 100}%`,
            background: 'linear-gradient(90deg, rgb(var(--accent)) 0%, rgb(var(--forest)) 100%)',
            borderRadius: 999,
            transition: 'width .25s',
          }} />
        </div>
        <span style={{ fontSize: 12, color: 'rgb(var(--muted))' }}>{ordered.length - idx - 1} after this</span>
      </div>

      {/* Card */}
      <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '0 32px 32px' }}>
        <button onClick={goPrev} disabled={idx === 0}
          aria-label="Previous task"
          style={{
            width: 48, height: 48, borderRadius: '50%',
            background: 'rgb(var(--surface))', border: '1px solid rgb(var(--rule))',
            fontSize: 20, color: 'rgb(var(--ink-2))',
            display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
            opacity: idx === 0 ? 0.3 : 1, cursor: idx === 0 ? 'default' : 'pointer',
            boxShadow: 'var(--sh-sm)', flexShrink: 0,
            transition: 'background-color .14s, transform .06s',
          }}
          onMouseEnter={e => { if (idx > 0) e.currentTarget.style.background = 'rgb(var(--paper-2))'; }}
          onMouseLeave={e => { if (idx > 0) e.currentTarget.style.background = 'rgb(var(--surface))'; }}
        >‹</button>

        <article key={t.id} className="fadein" style={{
          flex: 1, maxWidth: 720, marginInline: 24,
          background: 'rgb(var(--surface))', border: '1px solid rgb(var(--rule))',
          borderRadius: 18, padding: '32px 36px',
          boxShadow: 'var(--sh-md)',
        }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 14, flexWrap: 'wrap' }}>
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
              <span style={{ width: 8, height: 8, background: sp.colour, borderRadius: 2 }} />
              <span style={{ fontSize: 12.5, fontWeight: 600, color: sp.colour }}>{sp.name}</span>
            </span>
            <span style={{ fontFamily: 'JetBrains Mono, monospace', fontSize: 11.5, color: 'rgb(var(--muted))' }}>{t.ticket}</span>
            <span style={{ flex: 1 }} />
            <PriorityDot p={t.priority} />
            <span style={{
              fontSize: 11, fontWeight: 700, color: 'rgb(var(--muted))', textTransform: 'uppercase', letterSpacing: '0.06em',
            }}>{t.priority}</span>
          </div>

          <h1 style={{
            margin: 0, fontSize: 30, fontWeight: 700, letterSpacing: '-0.025em', lineHeight: 1.2,
            color: isDone ? 'rgb(var(--muted))' : 'rgb(var(--ink))',
            textDecoration: isDone ? 'line-through' : 'none',
          }}>{t.title}</h1>

          {t.description && (
            <p style={{
              marginTop: 14, marginBottom: 0,
              fontSize: 15, color: 'rgb(var(--ink-2))', lineHeight: 1.6,
              maxHeight: 80, overflow: 'hidden', textOverflow: 'ellipsis',
              display: '-webkit-box', WebkitLineClamp: 3, WebkitBoxOrient: 'vertical',
            }}>{t.description.replace(/[*`]/g, '')}</p>
          )}

          <div style={{ display: 'flex', alignItems: 'center', gap: 18, marginTop: 22, flexWrap: 'wrap' }}>
            <StatPill label="Due"        value={t.dueDate ? new Date(t.dueDate + 'T00:00:00').toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric', month: 'short' }) : '—'} />
            <StatPill label="Estimate"   value={t.estimateMins > 0 ? fmtMins(t.estimateMins) : '—'} />
            <StatPill label="Subtasks"   value={subs > 0 ? `${done}/${subs}` : '—'} />
            <StatPill label="Comments"   value={t.comments.length || '—'} />
            <span style={{ flex: 1 }} />
            {t.assignees.length > 0 && <AvatarStack ids={t.assignees} size={26} />}
          </div>

          <div style={{ marginTop: 28, display: 'flex', gap: 10, alignItems: 'center' }}>
            <button onClick={complete} className="btn btn-accent" style={{ fontSize: 14, padding: '10px 18px' }}>
              ✓ Mark done {isDone ? '· already' : ''}
            </button>
            <button onClick={() => onStartFocus?.(t)} className="btn btn-secondary" style={{ fontSize: 13, padding: '10px 16px' }}>Focus mode →</button>
            <span style={{ flex: 1 }} />
            <span style={{ fontSize: 12, color: 'rgb(var(--muted))', fontFamily: 'JetBrains Mono, monospace' }}>
              <kbd style={{ background: 'rgb(var(--paper-2))', padding: '2px 6px', borderRadius: 4, fontWeight: 600 }}>←</kbd>
              {' '}prev · next{' '}
              <kbd style={{ background: 'rgb(var(--paper-2))', padding: '2px 6px', borderRadius: 4, fontWeight: 600 }}>→</kbd>
            </span>
          </div>
        </article>

        <button onClick={goNext} disabled={idx === ordered.length - 1}
          aria-label="Next task"
          style={{
            width: 48, height: 48, borderRadius: '50%',
            background: 'rgb(var(--surface))', border: '1px solid rgb(var(--rule))',
            fontSize: 20, color: 'rgb(var(--ink-2))',
            display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
            opacity: idx === ordered.length - 1 ? 0.3 : 1, cursor: idx === ordered.length - 1 ? 'default' : 'pointer',
            boxShadow: 'var(--sh-sm)', flexShrink: 0,
            transition: 'background-color .14s, transform .06s',
          }}
          onMouseEnter={e => { if (idx < ordered.length - 1) e.currentTarget.style.background = 'rgb(var(--paper-2))'; }}
          onMouseLeave={e => { if (idx < ordered.length - 1) e.currentTarget.style.background = 'rgb(var(--surface))'; }}
        >›</button>
      </div>

      {/* Mini-strip of upcoming */}
      <div style={{ padding: '0 32px 20px' }}>
        <p style={{ margin: '0 0 8px', fontSize: 11, fontWeight: 600, color: 'rgb(var(--muted))', letterSpacing: '0.04em', textTransform: 'uppercase' }}>Up next</p>
        <div style={{ display: 'flex', gap: 8, overflowX: 'auto', paddingBottom: 4 }}>
          {ordered.slice(idx + 1, idx + 6).map(n => (
            <button key={n.id} onClick={() => onSelect(n.id)} style={{
              padding: '7px 12px', flexShrink: 0,
              background: 'rgb(var(--surface))', border: '1px solid rgb(var(--rule))',
              borderRadius: 999, fontSize: 12, fontWeight: 500,
              display: 'inline-flex', alignItems: 'center', gap: 6,
              maxWidth: 260,
              color: 'rgb(var(--ink-2))',
            }}
              onMouseEnter={e => e.currentTarget.style.background = 'rgb(var(--paper-2))'}
              onMouseLeave={e => e.currentTarget.style.background = 'rgb(var(--surface))'}>
              <PriorityDot p={n.priority} />
              <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{n.title}</span>
            </button>
          ))}
        </div>
      </div>
    </div>
  );
}

function StatPill({ label, value }) {
  return (
    <div>
      <div style={{ fontSize: 10.5, fontWeight: 600, color: 'rgb(var(--muted))', letterSpacing: '0.05em', textTransform: 'uppercase' }}>{label}</div>
      <div style={{ fontSize: 13.5, fontWeight: 600, color: 'rgb(var(--ink))', marginTop: 2, fontFamily: typeof value === 'number' ? 'JetBrains Mono, monospace' : 'inherit' }}>{value}</div>
    </div>
  );
}

/* ─────────── Cards (Pinterest masonry) ─────────── */
function CardsView({ tasks, onSelect, selectedId, onStatusChange, compact = false }) {
  return (
    <div style={{ flex: 1, overflowY: 'auto', background: 'rgb(var(--bg))', padding: '16px 24px 60px' }}>
      <div style={{
        columnCount: compact ? 4 : 3, columnGap: 12,
      }}>
        {tasks.map(t => <BigCard key={t.id} task={t} selected={t.id === selectedId} onClick={() => onSelect(t.id)} onComplete={() => onStatusChange?.(t.id, 6)} compact={compact} />)}
      </div>
      {tasks.length === 0 && (
        <div style={{ textAlign: 'center', padding: 80, color: 'rgb(var(--muted))' }}>
          <div style={{ fontSize: 36, marginBottom: 12 }}>🌸</div>
          <p style={{ fontSize: 14, margin: 0 }}>No tasks here.</p>
        </div>
      )}
    </div>
  );
}

function BigCard({ task, selected, onClick, onComplete, compact = false }) {
  const sp = spaceById(task.spaceId);
  const st = statusById(task.statusId);
  if (compact) {
    return (
      <TaskHoverArea task={task}>
        <article onClick={onClick} onDoubleClick={(e) => { e.stopPropagation(); window.__openTaskModal?.(task.id); }} style={{
          breakInside: 'avoid', marginBottom: 8,
          background: 'rgb(var(--surface))',
          border: selected ? '1.5px solid rgb(var(--accent))' : '1px solid rgb(var(--rule))',
          borderLeft: `3px solid ${sp.colour}`,
          borderRadius: 8, padding: '8px 10px',
          boxShadow: 'var(--sh-sm)', cursor: 'pointer',
          display: 'flex', alignItems: 'center', gap: 8,
        }}>
          <PriorityDot p={task.priority} />
          <span style={{ flex: 1, fontSize: 13, fontWeight: 500, color: 'rgb(var(--ink))', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{task.title}</span>
          <span className="pip" style={{ background: st.colour, width: 6, height: 6 }} title={st.title} />
          {task.assignees.length > 0 && <AvatarStack ids={task.assignees} size={18} max={2} />}
        </article>
      </TaskHoverArea>
    );
  }
  const subs = (task.subtasks || []).length + (task.checklist || []).length;
  const done = (task.subtasks || []).filter(s => s.done).length + (task.checklist || []).filter(c => c.done).length;
  const isDone = task.statusId === 6;

  return (
    <article onClick={onClick} onDoubleClick={(e) => { e.stopPropagation(); window.__openTaskModal?.(task.id); }} style={{
      breakInside: 'avoid', marginBottom: 14,
      background: 'rgb(var(--surface))',
      border: '1px solid ' + (selected ? 'rgb(var(--accent))' : 'rgb(var(--rule))'),
      borderRadius: 14,
      boxShadow: selected ? '0 0 0 3px rgb(var(--accent) / 0.12), var(--sh-md)' : 'var(--sh-sm)',
      cursor: 'pointer',
      overflow: 'hidden',
      transition: 'box-shadow .14s, transform .08s',
    }}
      onMouseEnter={e => { if (!selected) e.currentTarget.style.boxShadow = 'var(--sh-md)'; }}
      onMouseLeave={e => { if (!selected) e.currentTarget.style.boxShadow = 'var(--sh-sm)'; }}
    >
      {/* Coloured top band */}
      <div style={{
        height: 6,
        background: `linear-gradient(90deg, ${sp.colour} 0%, ${st.colour} 100%)`,
      }} />

      <div style={{ padding: '16px 18px' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
          <span style={{ fontFamily: 'JetBrains Mono, monospace', fontSize: 11, color: 'rgb(var(--muted))', fontWeight: 600 }}>{task.ticket}</span>
          <span style={{ flex: 1 }} />
          {task.important && <span style={{ color: 'rgb(var(--warm))', fontSize: 13 }}>★</span>}
          <span style={{
            display: 'inline-flex', alignItems: 'center', gap: 5,
            padding: '2px 9px', background: st.colour + '14', color: st.colour,
            fontSize: 10.5, fontWeight: 700, borderRadius: 999,
          }}>
            <span style={{ width: 6, height: 6, background: st.colour, borderRadius: '50%' }} />
            {st.title}
          </span>
        </div>

        <div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, marginBottom: 12 }}>
          <button onClick={e => { e.stopPropagation(); onComplete(); }} style={{
            width: 22, height: 22, borderRadius: '50%',
            border: '1.5px solid ' + (isDone ? 'rgb(var(--forest))' : 'rgb(var(--rule-2))'),
            background: isDone ? 'rgb(var(--forest))' : 'transparent',
            flexShrink: 0, marginTop: 2,
            display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
            color: 'white', fontSize: 12, fontWeight: 700,
          }}>{isDone && '✓'}</button>
          <h3 style={{
            margin: 0, fontSize: 16, fontWeight: 600, letterSpacing: '-0.015em', lineHeight: 1.35,
            color: isDone ? 'rgb(var(--muted))' : 'rgb(var(--ink))',
            textDecoration: isDone ? 'line-through' : 'none',
            flex: 1, minWidth: 0,
          }}>{task.title}</h3>
        </div>

        {task.description && (
          <p style={{
            margin: '0 0 12px', fontSize: 13, color: 'rgb(var(--ink-2))', lineHeight: 1.5,
            display: '-webkit-box', WebkitLineClamp: 3, WebkitBoxOrient: 'vertical',
            overflow: 'hidden',
          }}>{task.description.replace(/[*`]/g, '').slice(0, 200)}</p>
        )}

        {task.tags.length > 0 && (
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginBottom: 12 }}>
            {task.tags.map(id => <TagChip key={id} tag={tagById(id)} />)}
          </div>
        )}

        {subs > 0 && (
          <div style={{ marginBottom: 12 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <span style={{ flex: 1 }}>
                <ProgressBar value={(done/subs)*100} height={4} colour={done === subs ? 'rgb(var(--forest))' : 'rgb(var(--accent))'} />
              </span>
              <span className="num" style={{ fontSize: 11, color: 'rgb(var(--muted))', fontWeight: 600 }}>{done}/{subs}</span>
            </div>
          </div>
        )}

        <div style={{
          display: 'flex', alignItems: 'center', gap: 10,
          padding: '10px 0 0', borderTop: '1px solid rgb(var(--rule) / 0.5)',
          marginTop: 4,
        }}>
          <PriorityDot p={task.priority} />
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 11.5, color: 'rgb(var(--muted))', fontWeight: 500 }}>
            <span style={{ width: 6, height: 6, background: sp.colour, borderRadius: 2 }} />
            {sp.name}
          </span>
          <span style={{ flex: 1 }} />
          {task.dueDate && <DueChip iso={task.dueDate} />}
          {task.assignees.length > 0 && <AvatarStack ids={task.assignees} max={2} size={22} />}
        </div>
      </div>
    </article>
  );
}

Object.assign(window, { AgendaView, FocusView, CardsView });
