const { useState, useEffect, useCallback, useRef } = React;

// ── IMAGE PLACEHOLDER ──────────────────────────────────────────────────
function ImgPlaceholder({ category, color }) {
  return (
    <div style={{
      position: 'absolute', inset: 0,
      background: `linear-gradient(135deg, ${color}18 0%, ${color}08 100%)`,
      display: 'flex', alignItems: 'flex-end', padding: '16px 18px',
    }}>
      <div style={{ position: 'absolute', top: '14px', right: '14px', width: '32px', height: '32px', borderRadius: '50%', background: color + '22', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <div style={{ width: '8px', height: '8px', borderRadius: '50%', background: color }} />
      </div>
      <span style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', color: color, letterSpacing: '0.1em', textTransform: 'uppercase', fontWeight: 500 }}>
        {category}
      </span>
    </div>
  );
}

function CatPill({ category, color }) {
  return <span className="cat-pill" style={{ '--cat-color': color }}>{category}</span>;
}

// ── ARTICLE IMAGE ──────────────────────────────────────────────────────
// Lazy-fetches og:image for the article via Microlink when the card
// scrolls into view. Falls back to the styled placeholder on miss.
function ArticleImage({ article }) {
  const [src, setSrc] = React.useState(article.thumbnail || null);
  const [errored, setErrored] = React.useState(false);
  const ref = React.useRef(null);

  React.useEffect(() => {
    if (src || !article.link) return;
    let cancelled = false;
    const node = ref.current;
    if (!node || !('IntersectionObserver' in window)) {
      window.fetchArticleImage(article.link).then(u => { if (!cancelled && u) setSrc(u); });
      return () => { cancelled = true; };
    }
    const io = new IntersectionObserver((entries) => {
      if (entries.some(e => e.isIntersecting)) {
        io.disconnect();
        window.fetchArticleImage(article.link).then(u => { if (!cancelled && u) setSrc(u); });
      }
    }, { rootMargin: '200px' });
    io.observe(node);
    return () => { cancelled = true; io.disconnect(); };
  }, [article.link]);

  if (src && !errored) {
    return (
      <img
        ref={ref}
        src={src}
        alt=""
        loading="lazy"
        style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' }}
        onError={() => setErrored(true)}
      />
    );
  }
  return (
    <div ref={ref} style={{ position: 'absolute', inset: 0 }}>
      <ImgPlaceholder category={article.category} color={article.categoryColor} />
    </div>
  );
}

// ── META ROW ───────────────────────────────────────────────────────────
function MetaRow({ article, bookmarks, onBookmark, compact }) {
  const saved = bookmarks.includes(article.guid);
  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: '10px',
      fontFamily: 'var(--font-mono)', fontSize: '10px',
      color: 'var(--ink3)', letterSpacing: '0.04em',
      marginTop: compact ? '8px' : '14px', textTransform: 'uppercase',
    }}>
      <span style={{ color: 'var(--ink2)', fontWeight: 500 }}>{article.source}</span>
      <span style={{ opacity: 0.5 }}>/</span>
      <span>{formatAge(article.pubDate)}</span>
      <span style={{ opacity: 0.5 }}>/</span>
      <span>{article.readTime}m read</span>
      <span style={{ flex: 1 }} />
      <button onClick={e => { e.preventDefault(); onBookmark(article.guid); }}
        style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '4px', color: saved ? 'var(--red)' : 'var(--ink3)', transition: 'all 0.18s', display: 'flex', alignItems: 'center', margin: '-4px' }}>
        <svg width="13" height="13" viewBox="0 0 24 24" fill={saved ? 'currentColor' : 'none'} stroke="currentColor" strokeWidth="2">
          <path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/>
        </svg>
      </button>
    </div>
  );
}

// ── AI DIGEST ──────────────────────────────────────────────────────────
function AiDigest({ id, title, summaries, onSave }) {
  const [loading, setLoading] = useState(false);
  const done = useRef(false);
  const existing = summaries[id];

  useEffect(() => {
    if (existing || done.current || !title || !window.claude) return;
    done.current = true;
    setLoading(true);
    window.claude.complete(
      `Give one sharp, insightful sentence (max 18 words) about why this article matters today: "${title}". Only the sentence, no quotes.`
    ).then(txt => { onSave(id, txt.trim()); setLoading(false); })
     .catch(() => setLoading(false));
  }, [id, title]);

  if (!existing && !loading) return null;
  return (
    <div style={{
      display: 'flex', alignItems: 'flex-start', gap: '10px',
      padding: '14px 0', margin: '16px 0',
      borderTop: '1px solid var(--rule)', borderBottom: '1px solid var(--rule)',
    }}>
      <span style={{
        fontFamily: 'var(--font-mono)', fontSize: '9px', fontWeight: 600,
        letterSpacing: '0.1em', color: 'var(--red)',
        padding: '2px 6px', border: '1px solid var(--red)', borderRadius: '3px',
        textTransform: 'uppercase', whiteSpace: 'nowrap', marginTop: '1px',
      }}>Summary</span>
      <p style={{
        fontFamily: 'var(--font-display)', fontStyle: 'italic',
        fontSize: '16px', color: 'var(--ink)', lineHeight: 1.4, margin: 0, flex: 1,
      }}>
        {loading
          ? <span style={{ display: 'block', height: '16px', width: '85%', borderRadius: '2px', animation: 'shimmer 1.4s infinite' }} />
          : existing}
      </p>
    </div>
  );
}

// ── SKELETONS ──────────────────────────────────────────────────────────
function Skel({ w = '100%', h = '14px', mb = '8px' }) {
  return <div style={{ width: w, height: h, marginBottom: mb, borderRadius: '3px', animation: 'shimmer 1.4s infinite' }} />;
}
function SkeletonCard({ image = true }) {
  return (
    <div>
      {image && <div style={{ width: '100%', aspectRatio: '16/10', marginBottom: '18px', borderRadius: '4px', animation: 'shimmer 1.4s infinite' }} />}
      <Skel w="60px" h="10px" mb="12px" />
      <Skel w="92%" h="24px" mb="6px" />
      <Skel w="72%" h="24px" mb="14px" />
      <Skel w="82%" h="12px" mb="5px" />
      <Skel w="65%" h="12px" mb="16px" />
      <Skel w="140px" h="10px" mb="0" />
    </div>
  );
}

// ── HERO ───────────────────────────────────────────────────────────────
function HeroSection({ articles, bookmarks, onBookmark, summaries, onSummary }) {
  const [main, s1, s2] = articles;
  if (!main) return (
    <div className="hero-grid">
      <SkeletonCard />
      <div><SkeletonCard image /><div style={{borderTop:'1px solid var(--rule)',paddingTop:'32px',marginTop:'32px'}}><SkeletonCard image /></div></div>
    </div>
  );
  return (
    <div className="hero-grid fade-up">
      <div className="hero-main">
        <div className="thumb-box" style={{ aspectRatio: '16/10', marginBottom: '24px' }}>
          <ArticleImage article={main} />
        </div>
        <CatPill category={main.category} color={main.categoryColor} />
        <h2 className="hed-hero"><a href={main.link} target="_blank" rel="noreferrer">{main.title}</a></h2>
        <AiDigest id={main.guid} title={main.title} summaries={summaries} onSave={onSummary} />
        <p className="body-text" style={{ WebkitLineClamp: 3, display: '-webkit-box', WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>{main.description?.slice(0, 280)}</p>
        <MetaRow article={main} bookmarks={bookmarks} onBookmark={onBookmark} />
      </div>
      <div className="hero-side">
        {[s1, s2].filter(Boolean).map((art) => (
          <div key={art.guid}>
            <div className="thumb-box" style={{ aspectRatio: '4/3', marginBottom: '16px' }}>
              <ArticleImage article={art} />
            </div>
            <CatPill category={art.category} color={art.categoryColor} />
            <h3 className="hed-std"><a href={art.link} target="_blank" rel="noreferrer">{art.title}</a></h3>
            <AiDigest id={art.guid} title={art.title} summaries={summaries} onSave={onSummary} />
            <MetaRow article={art} bookmarks={bookmarks} onBookmark={onBookmark} />
          </div>
        ))}
      </div>
    </div>
  );
}

// ── TRENDING ───────────────────────────────────────────────────────────
function TrendingSection({ articles, bookmarks, onBookmark }) {
  const items = articles.slice(0, 4);
  if (!items.length) return <div className="four-grid">{[0,1,2,3].map(i => <SkeletonCard key={i} image={false} />)}</div>;
  return (
    <div className="four-grid fade-up">
      {items.map((art, i) => (
        <div key={art.guid} style={{ position: 'relative' }}>
          <div style={{
            fontFamily: 'var(--font-display)', fontSize: '48px', fontWeight: 400,
            color: i === 0 ? 'var(--red)' : 'var(--rule-strong)', lineHeight: 0.9,
            marginBottom: '14px', fontStyle: 'italic',
          }}>
            {String(i + 1).padStart(2, '0')}
          </div>
          <div style={{ height: '1px', background: i === 0 ? 'var(--red)' : 'var(--rule)', marginBottom: '16px' }} />
          <CatPill category={art.category} color={art.categoryColor} />
          <h4 className="hed-compact"><a href={art.link} target="_blank" rel="noreferrer">{art.title}</a></h4>
          <p className="body-text" style={{ fontSize: '13px', margin: '4px 0 12px', WebkitLineClamp: 2, display: '-webkit-box', WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>{art.description?.slice(0, 130)}</p>
          <MetaRow article={art} bookmarks={bookmarks} onBookmark={onBookmark} compact />
        </div>
      ))}
    </div>
  );
}

// ── DEEP READS ─────────────────────────────────────────────────────────
function DeepReads({ articles, bookmarks, onBookmark, summaries, onSummary }) {
  const items = articles.slice(0, 3);
  if (!items.length) return <div className="three-grid">{[0,1,2].map(i => <SkeletonCard key={i} />)}</div>;
  return (
    <div className="three-grid fade-up">
      {items.map(art => (
        <div key={art.guid}>
          <div className="thumb-box" style={{ aspectRatio: '4/3', marginBottom: '18px' }}>
            <ArticleImage article={art} />
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '10px' }}>
            <span style={{
              fontFamily: 'var(--font-mono)', fontSize: '9px', fontWeight: 600,
              letterSpacing: '0.12em', textTransform: 'uppercase',
              color: 'var(--ink)', background: 'var(--bg2)',
              padding: '4px 8px', borderRadius: '3px',
            }}>Long Read</span>
            <span style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', color: 'var(--ink3)' }}>
              {art.readTime} min
            </span>
          </div>
          <CatPill category={art.category} color={art.categoryColor} />
          <h3 className="hed-std"><a href={art.link} target="_blank" rel="noreferrer">{art.title}</a></h3>
          <AiDigest id={art.guid + '_dr'} title={art.title} summaries={summaries} onSave={onSummary} />
          <p className="body-text" style={{ WebkitLineClamp: 3, display: '-webkit-box', WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>{art.description?.slice(0, 200)}</p>
          <MetaRow article={art} bookmarks={bookmarks} onBookmark={onBookmark} />
        </div>
      ))}
    </div>
  );
}

// ── QUICK HITS ─────────────────────────────────────────────────────────
function QuickHits({ articles }) {
  const items = articles.slice(0, 8);
  if (!items.length) return <div className="two-grid" style={{ paddingBottom: '80px' }}>{[0,1,2,3].map(i => <SkeletonCard key={i} image={false} />)}</div>;
  return (
    <div className="two-grid fade-up" style={{ paddingBottom: '80px' }}>
      {items.map((art, i) => (
        <a key={art.guid} href={art.link} target="_blank" rel="noreferrer"
          style={{ display: 'flex', gap: '18px', borderBottom: '1px solid var(--rule)', padding: '22px 0', alignItems: 'flex-start', textDecoration: 'none', color: 'inherit', transition: 'transform 0.2s' }}
          onMouseEnter={e => { e.currentTarget.style.transform = 'translateX(4px)'; }}
          onMouseLeave={e => { e.currentTarget.style.transform = 'translateX(0)'; }}>
          <span style={{
            fontFamily: 'var(--font-mono)', fontSize: '11px', fontWeight: 500,
            color: art.categoryColor, lineHeight: 1.4, minWidth: '24px', paddingTop: '1px',
          }}>
            {String(i + 1).padStart(2, '0')}
          </span>
          <div style={{ flex: 1 }}>
            <CatPill category={art.category} color={art.categoryColor} />
            <p style={{
              fontFamily: 'var(--font-ui)', fontWeight: 500, fontSize: '15px',
              lineHeight: 1.35, letterSpacing: '-0.01em', color: 'var(--ink)',
              margin: '2px 0 8px',
            }}>{art.title}</p>
            <div style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', color: 'var(--ink3)', letterSpacing: '0.04em', textTransform: 'uppercase' }}>
              {art.source} · {formatAge(art.pubDate)}
            </div>
          </div>
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{ color: 'var(--ink3)', marginTop: '4px' }}>
            <path d="M7 17L17 7M7 7h10v10"/>
          </svg>
        </a>
      ))}
    </div>
  );
}

// ── SECTION HEADER ─────────────────────────────────────────────────────
function SectionHdr({ label, count }) {
  return (
    <div style={{ display: 'flex', alignItems: 'baseline', gap: '16px', padding: '88px 0 36px' }}>
      <span style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', fontWeight: 500, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'var(--ink3)' }}>
        §{count}
      </span>
      <h2 style={{
        fontFamily: 'var(--font-display)', fontWeight: 400, fontStyle: 'italic',
        fontSize: 'clamp(32px, 4vw, 48px)', lineHeight: 1, letterSpacing: '-0.02em',
        color: 'var(--ink)', margin: 0,
      }}>{label}</h2>
      <div style={{ flex: 1, height: '1px', background: 'var(--rule)', marginLeft: '12px' }} />
    </div>
  );
}

// ── LIVE CLOCK ─────────────────────────────────────────────────────────
function useLiveClock() {
  const [now, setNow] = useState(new Date());
  useEffect(() => {
    const id = setInterval(() => setNow(new Date()), 30000);
    return () => clearInterval(id);
  }, []);
  return now;
}

// ── MASTHEAD ───────────────────────────────────────────────────────────
function Masthead({ edition, onEdition, dark, onToggleDark, bookmarkCount, onRefresh, dataSource, topic, onTopic, onOpenSources }) {
  const now = useLiveClock();
  const today = now.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' });
  const time = now.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
  const issueNum = Math.floor((now - new Date('2026-01-01')) / 86400000) + 1;

  return (
    <header>
      {/* Top strip */}
      <div className="shell" style={{
        padding: '14px 40px', display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        fontFamily: 'var(--font-mono)', fontSize: '10px', color: 'var(--ink3)',
        letterSpacing: '0.08em', textTransform: 'uppercase',
      }}>
        <div style={{ display: 'flex', gap: '20px', alignItems: 'center' }}>
          <span>{today}</span>
          <span style={{ color: 'var(--ink2)' }}>·</span>
          <span>{time}</span>
        </div>
        <div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
          {bookmarkCount > 0 && (
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: '5px', color: 'var(--red)', padding: '4px 10px', border: '1px solid var(--red)', borderRadius: '100px' }}>
              <svg width="9" height="9" viewBox="0 0 24 24" fill="currentColor"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/></svg>
              {bookmarkCount}
            </span>
          )}
          <button className="icon-btn" onClick={onRefresh}>
            <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M8 16H3v5"/></svg>
            Refresh
          </button>
          <button className="icon-btn" onClick={onToggleDark}>
            {dark ? '☀' : '◐'} {dark ? 'Light' : 'Dark'}
          </button>
        </div>
      </div>

      {/* Thin divider */}
      <div style={{ borderTop: '1px solid var(--rule)' }} />

      {/* Logo block */}
      <div className="shell" style={{ padding: '36px 40px 24px', textAlign: 'center', position: 'relative' }}>
        <div style={{
          fontFamily: 'var(--font-mono)', fontSize: '10px', letterSpacing: '0.2em',
          textTransform: 'uppercase', color: 'var(--ink3)', marginBottom: '20px',
        }}>
          Vol. I · Issue N°{issueNum} ·{' '}
          <span style={{ color: 'var(--red)', fontWeight: 600 }}>
            ● Live from {dataSource?.meta?.source || 'your sources'} · Last 24 hours
          </span>
        </div>

        <h1 style={{
          fontFamily: 'var(--font-display)', fontWeight: 400,
          fontSize: 'clamp(72px, 12vw, 180px)',
          letterSpacing: '-0.04em', lineHeight: 0.9, color: 'var(--ink)',
          margin: 0, position: 'relative', display: 'inline-block',
        }}>
          <span style={{ fontStyle: 'italic' }}>The</span>{' '}
          <span>Daily</span>
        </h1>

        {/* Edition toggle */}
        <div style={{ marginTop: '24px', display: 'flex', justifyContent: 'center' }}>
          <div className="edition-toggle">
            <button className={edition === 'am' ? 'active' : ''} onClick={() => onEdition('am')}>
              AM Edition
            </button>
            <button className={edition === 'pm' ? 'active' : ''} onClick={() => onEdition('pm')}>
              PM Edition
            </button>
          </div>
        </div>

        {/* Categories */}
        <div style={{
          marginTop: '28px', display: 'flex', justifyContent: 'center', gap: '18px',
          flexWrap: 'wrap', alignItems: 'center', fontFamily: 'var(--font-mono)', fontSize: '10px',
          letterSpacing: '0.14em', textTransform: 'uppercase',
        }}>
          {(() => {
            const chips = [{ key: 'home', label: 'Home' }, ...(window.TOPIC_GROUPS || [])];
            return chips.map((c, i, arr) => (
              <React.Fragment key={c.key}>
                <button
                  onClick={() => onTopic(c.key === 'home' ? null : c.key)}
                  style={{
                    background: 'none', border: 'none', padding: '4px 2px', cursor: 'pointer',
                    fontFamily: 'inherit', fontSize: 'inherit', letterSpacing: 'inherit',
                    textTransform: 'inherit',
                    color: (topic === c.key || (c.key === 'home' && !topic)) ? 'var(--red)' : 'var(--ink2)',
                    fontWeight: (topic === c.key || (c.key === 'home' && !topic)) ? 600 : 400,
                    borderBottom: (topic === c.key || (c.key === 'home' && !topic)) ? '1px solid var(--red)' : '1px solid transparent',
                    transition: 'color 0.15s',
                  }}
                  onMouseEnter={e => { if (!(topic === c.key || (c.key === 'home' && !topic))) e.currentTarget.style.color = 'var(--ink)'; }}
                  onMouseLeave={e => { if (!(topic === c.key || (c.key === 'home' && !topic))) e.currentTarget.style.color = 'var(--ink2)'; }}
                >{c.label}</button>
                {i < arr.length - 1 && <span style={{ color: 'var(--rule-strong)' }}>◆</span>}
              </React.Fragment>
            ));
          })()}
          <span style={{ color: 'var(--rule-strong)' }}>◆</span>
          <button
            onClick={onOpenSources}
            style={{
              background: 'none', border: 'none', padding: '4px 2px', cursor: 'pointer',
              fontFamily: 'inherit', fontSize: 'inherit', letterSpacing: 'inherit',
              textTransform: 'inherit', color: 'var(--ink3)', fontStyle: 'italic',
            }}
            onMouseEnter={e => e.currentTarget.style.color = 'var(--red)'}
            onMouseLeave={e => e.currentTarget.style.color = 'var(--ink3)'}
            title="Add or remove sources"
          >+ Sources</button>
        </div>
      </div>

      {/* Thick + thin rule */}
      <div style={{ borderTop: '2px solid var(--ink)' }} />
      <div style={{ borderTop: '1px solid var(--ink)', marginTop: '3px' }} />

      {/* Nav */}
      <nav className="shell" style={{
        padding: '14px 40px', display: 'flex', gap: '32px', alignItems: 'center',
        fontFamily: 'var(--font-mono)', fontSize: '10px', letterSpacing: '0.14em',
        textTransform: 'uppercase', borderBottom: '1px solid var(--rule)',
      }}>
        {['Top Stories', 'Trending', 'Deep Reads', 'Quick Hits'].map(label => (
          <button key={label} onClick={() => {
            const el = document.getElementById(label.toLowerCase().replace(' ', '-'));
            if (el) window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - 80, behavior: 'smooth' });
          }} style={{
            background: 'none', border: 'none', cursor: 'pointer', padding: 0,
            fontFamily: 'inherit', fontSize: 'inherit', letterSpacing: 'inherit',
            textTransform: 'inherit', fontWeight: 500, color: 'var(--ink2)', transition: 'color 0.15s',
          }}
            onMouseEnter={e => e.target.style.color='var(--red)'}
            onMouseLeave={e => e.target.style.color='var(--ink2)'}>{label}</button>
        ))}
        <span style={{ flex: 1 }} />
        <span style={{ color: 'var(--ink3)', fontWeight: 400 }}>{edition.toUpperCase()} · {time}</span>
      </nav>
    </header>
  );
}

// ── SOURCES PANEL (standalone modal) ───────────────────────────────────
function SourcesPanel({ open, onClose, onSourcesChange }) {
  const [sources, setSources] = useState(() => window.loadSources ? window.loadSources() : []);
  const [newUrl, setNewUrl] = useState('');
  const [newName, setNewName] = useState('');
  if (!open) return null;

  const persist = (next) => {
    setSources(next);
    window.saveSources(next);
    onSourcesChange && onSourcesChange();
  };
  const removeSrc = (key) => persist(sources.filter(s => s.key !== key));
  const addSrc = () => {
    if (!newUrl.trim() || !newName.trim()) return;
    const key = newName.toLowerCase().replace(/\W+/g, '').slice(0, 10) + '_' + Date.now().toString(36).slice(-3);
    persist([...sources, { key, name: newName.trim(), url: newUrl.trim(), section: 'trending', color: '#4f46e5' }]);
    setNewUrl(''); setNewName('');
  };
  const resetDefaults = () => {
    window.resetSources();
    persist(window.DEFAULT_SOURCES);
  };

  const inputStyle = {
    width: '100%', padding: '8px 10px', fontSize: '12px', fontFamily: 'var(--font-mono)',
    border: '1px solid var(--rule-strong)', borderRadius: '4px', background: 'var(--bg)',
    color: 'var(--ink)', outline: 'none', boxSizing: 'border-box',
  };

  return (
    <div
      onClick={onClose}
      style={{
        position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.38)',
        zIndex: 400, display: 'flex', alignItems: 'center', justifyContent: 'center',
        animation: 'fadeIn 0.2s',
      }}
    >
      <div
        onClick={e => e.stopPropagation()}
        style={{
          background: 'var(--surface)', border: '1px solid var(--rule-strong)', borderRadius: '10px',
          padding: '28px 32px', width: '460px', maxWidth: 'calc(100vw - 40px)',
          maxHeight: 'calc(100vh - 80px)', overflowY: 'auto',
          boxShadow: '0 24px 64px rgba(0,0,0,0.25)',
          fontFamily: 'var(--font-ui)',
        }}
      >
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '4px' }}>
          <div>
            <div style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', fontWeight: 600, letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--ink3)', marginBottom: '6px' }}>◆ Your Sources</div>
            <div style={{ fontFamily: 'var(--font-display)', fontStyle: 'italic', fontSize: '22px', color: 'var(--ink)' }}>Edit your feed list</div>
          </div>
          <button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--ink3)', fontSize: '24px', lineHeight: 1, padding: 0 }}>×</button>
        </div>
        <div style={{ fontSize: '13px', color: 'var(--ink2)', margin: '10px 0 18px', fontFamily: 'var(--font-ui)' }}>
          Add any RSS feed URL. Changes auto-refetch the edition.
        </div>

        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: '10px' }}>
          <div style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', color: 'var(--ink3)', letterSpacing: '0.1em', textTransform: 'uppercase' }}>{sources.length} sources</div>
          <button onClick={resetDefaults} style={{ background: 'none', border: 'none', fontFamily: 'var(--font-mono)', fontSize: '10px', color: 'var(--ink3)', cursor: 'pointer', letterSpacing: '0.1em', textTransform: 'uppercase' }}>↺ Reset to defaults</button>
        </div>
        <div style={{ maxHeight: '280px', overflowY: 'auto', border: '1px solid var(--rule)', borderRadius: '6px', marginBottom: '18px' }}>
          {sources.map(s => (
            <div key={s.key} style={{ display: 'flex', alignItems: 'center', gap: '10px', padding: '10px 14px', borderBottom: '1px solid var(--rule)', fontSize: '13px' }}>
              <span style={{ width: '8px', height: '8px', borderRadius: '50%', background: s.color, flexShrink: 0 }} />
              <span style={{ flex: 1, fontFamily: 'var(--font-ui)', fontWeight: 500, color: 'var(--ink)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{s.name}</span>
              <span style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', color: 'var(--ink3)', maxWidth: '180px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{s.url.replace(/^https?:\/\//, '').slice(0, 30)}</span>
              <button onClick={() => removeSrc(s.key)} title="Remove" style={{ background: 'none', border: 'none', color: 'var(--ink3)', cursor: 'pointer', fontSize: '18px', padding: 0, lineHeight: 1 }}>×</button>
            </div>
          ))}
        </div>

        <div style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', color: 'var(--ink3)', letterSpacing: '0.1em', textTransform: 'uppercase', marginBottom: '8px' }}>+ Add new source</div>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 2fr', gap: '8px', marginBottom: '10px' }}>
          <input placeholder="Display name" value={newName} onChange={e => setNewName(e.target.value)} style={inputStyle} />
          <input placeholder="https://example.com/feed.xml" value={newUrl} onChange={e => setNewUrl(e.target.value)} style={inputStyle} />
        </div>
        <button
          onClick={addSrc}
          disabled={!newUrl.trim() || !newName.trim()}
          style={{
            width: '100%', padding: '10px', fontFamily: 'var(--font-mono)', fontSize: '11px',
            letterSpacing: '0.12em', textTransform: 'uppercase', background: 'var(--ink)',
            color: 'var(--bg)', border: 'none', borderRadius: '5px', cursor: 'pointer',
            opacity: (!newUrl.trim() || !newName.trim()) ? 0.35 : 1,
          }}
        >+ Add source</button>
      </div>
    </div>
  );
}

// ── TWEAKS PANEL ───────────────────────────────────────────────────────
function TweaksPanel({ visible, tweaks, onChange, onSourcesChange }) {
  const [sources, setSources] = useState(() => window.loadSources ? window.loadSources() : []);
  const [newUrl, setNewUrl] = useState('');
  const [newName, setNewName] = useState('');
  if (!visible) return null;

  const accents = [{ l: 'Red',    v: '#c8321f' }, { l: 'Indigo', v: '#4f46e5' }, { l: 'Teal',   v: '#0d9488' }, { l: 'Amber',  v: '#b45309' }];
  const scales  = [{ l: 'S', v: '0.9' }, { l: 'M', v: '1' }, { l: 'L', v: '1.1' }];

  const persist = (next) => {
    setSources(next);
    window.saveSources(next);
    window.TOPICS = next.map(s => ({ key: s.key, label: s.name, color: s.color, section: s.section, source: s.name }));
    onSourcesChange && onSourcesChange();
  };
  const removeSrc = (key) => persist(sources.filter(s => s.key !== key));
  const addSrc = () => {
    if (!newUrl.trim() || !newName.trim()) return;
    const key = newName.toLowerCase().replace(/\W+/g, '').slice(0, 10) + '_' + Date.now().toString(36).slice(-3);
    persist([...sources, { key, name: newName.trim(), url: newUrl.trim(), section: 'trending', color: '#4f46e5' }]);
    setNewUrl(''); setNewName('');
  };
  const resetDefaults = () => {
    window.resetSources();
    persist(window.DEFAULT_SOURCES);
  };

  const btnStyle = (active) => ({
    padding: '5px 10px', border: `1px solid ${active ? 'var(--red)' : 'var(--rule-strong)'}`,
    borderRadius: '4px', cursor: 'pointer', fontSize: '11px',
    background: active ? 'var(--bg2)' : 'var(--bg)',
    color: active ? 'var(--red)' : 'var(--ink2)',
    fontFamily: 'var(--font-mono)', fontWeight: active ? 500 : 400,
    transition: 'all 0.15s', letterSpacing: '0.02em',
  });
  const sectionLabel = { fontFamily: 'var(--font-mono)', fontSize: '9px', color: 'var(--ink3)', marginBottom: '8px', letterSpacing: '0.08em', textTransform: 'uppercase' };
  const inputStyle = {
    width: '100%', padding: '6px 8px', fontSize: '11px', fontFamily: 'var(--font-mono)',
    border: '1px solid var(--rule-strong)', borderRadius: '4px', background: 'var(--bg)',
    color: 'var(--ink)', outline: 'none', boxSizing: 'border-box',
  };
  return (
    <div style={{
      position: 'fixed', bottom: '24px', right: '24px',
      background: 'var(--surface)', border: '1px solid var(--rule-strong)', borderRadius: '10px',
      padding: '18px 20px', width: '280px', maxHeight: 'calc(100vh - 80px)', overflowY: 'auto',
      boxShadow: '0 12px 40px rgba(0,0,0,0.12)', zIndex: 100,
      fontFamily: 'var(--font-ui)',
    }}>
      <div style={{ fontFamily: 'var(--font-mono)', fontSize: '10px', fontWeight: 600, letterSpacing: '0.14em', textTransform: 'uppercase', color: 'var(--ink)', marginBottom: '14px' }}>◆ Tweaks</div>
      <div style={{ marginBottom: '14px' }}>
        <div style={sectionLabel}>Accent</div>
        <div style={{ display: 'flex', gap: '6px', flexWrap: 'wrap' }}>
          {accents.map(a => (
            <button key={a.v} style={btnStyle(tweaks.accent === a.v)} onClick={() => onChange({ ...tweaks, accent: a.v })}>
              <span style={{ display: 'inline-block', width: '7px', height: '7px', borderRadius: '50%', background: a.v, marginRight: '5px', verticalAlign: 'middle' }} />{a.l}
            </button>
          ))}
        </div>
      </div>
      <div style={{ marginBottom: '14px' }}>
        <div style={sectionLabel}>Type Size</div>
        <div style={{ display: 'flex', gap: '6px' }}>
          {scales.map(s => (
            <button key={s.v} style={btnStyle(tweaks.scale === s.v)} onClick={() => onChange({ ...tweaks, scale: s.v })}>{s.l}</button>
          ))}
        </div>
      </div>

      <div style={{ height: '1px', background: 'var(--rule)', margin: '4px -4px 14px' }} />

      <div>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: '8px' }}>
          <div style={{ ...sectionLabel, marginBottom: 0 }}>Sources · {sources.length}</div>
          <button onClick={resetDefaults} style={{ background: 'none', border: 'none', fontFamily: 'var(--font-mono)', fontSize: '9px', color: 'var(--ink3)', cursor: 'pointer', letterSpacing: '0.08em', textTransform: 'uppercase' }}>Reset</button>
        </div>
        <div style={{ maxHeight: '180px', overflowY: 'auto', border: '1px solid var(--rule)', borderRadius: '4px', marginBottom: '10px' }}>
          {sources.map(s => (
            <div key={s.key} style={{ display: 'flex', alignItems: 'center', gap: '6px', padding: '6px 8px', borderBottom: '1px solid var(--rule)', fontSize: '11px' }}>
              <span style={{ width: '6px', height: '6px', borderRadius: '50%', background: s.color, flexShrink: 0 }} />
              <span style={{ flex: 1, fontFamily: 'var(--font-ui)', color: 'var(--ink)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{s.name}</span>
              <button onClick={() => removeSrc(s.key)} title="Remove" style={{ background: 'none', border: 'none', color: 'var(--ink3)', cursor: 'pointer', fontSize: '14px', padding: 0, lineHeight: 1 }}>×</button>
            </div>
          ))}
        </div>
        <input placeholder="Source name" value={newName} onChange={e => setNewName(e.target.value)} style={{ ...inputStyle, marginBottom: '6px' }} />
        <input placeholder="RSS feed URL" value={newUrl} onChange={e => setNewUrl(e.target.value)} style={{ ...inputStyle, marginBottom: '8px' }} />
        <button onClick={addSrc} disabled={!newUrl.trim() || !newName.trim()} style={{ width: '100%', padding: '7px', fontFamily: 'var(--font-mono)', fontSize: '10px', letterSpacing: '0.1em', textTransform: 'uppercase', background: 'var(--ink)', color: 'var(--bg)', border: 'none', borderRadius: '4px', cursor: 'pointer', opacity: (!newUrl.trim() || !newName.trim()) ? 0.4 : 1 }}>
          + Add source
        </button>
      </div>
    </div>
  );
}

// ── TOAST ──────────────────────────────────────────────────────────────
function Toast({ msg, show }) {
  return (
    <div style={{
      position: 'fixed', top: '24px', left: '50%', transform: 'translateX(-50%)',
      background: 'var(--ink)', color: 'var(--bg)',
      padding: '10px 22px', borderRadius: '100px',
      fontFamily: 'var(--font-mono)', fontSize: '11px', letterSpacing: '0.06em',
      textTransform: 'uppercase',
      zIndex: 300, opacity: show ? 1 : 0, transition: 'opacity 0.3s', pointerEvents: 'none',
    }}>{msg}</div>
  );
}

// ── APP ────────────────────────────────────────────────────────────────
function App() {
  const initEdition = () => {
    const stored = localStorage.getItem('amdaily_edition');
    if (stored === 'am' || stored === 'pm') return stored;
    return new Date().getHours() < 14 ? 'am' : 'pm';
  };

  const [edition, setEdition] = useState(initEdition);
  const [dark, setDark] = useState(() => localStorage.getItem('amdaily_dark') === '1');
  const [articles, setArticles] = useState({ top: [], trending: [], deep: [], quick: [] });
  const [bookmarks, setBookmarks] = useState(() => loadBookmarks());
  const [summaries, setSummaries] = useState(() => loadSummaries());
  const [tweaksOn, setTweaksOn] = useState(false);
  const [tweaks, setTweaks] = useState(() => {
    try { return JSON.parse(localStorage.getItem('amdaily_tweaks') || 'null') || { accent: '#c8321f', scale: '1' }; }
    catch(e) { return { accent: '#c8321f', scale: '1' }; }
  });
  const [toast, setToast] = useState({ show: false, msg: '' });
  const [dataSource, setDataSource] = useState({ mode: 'curated', meta: null });
  const [topic, setTopic] = useState(null);
  const [sourcesPanelOpen, setSourcesPanelOpen] = useState(false);

  useEffect(() => {
    document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light');
    localStorage.setItem('amdaily_dark', dark ? '1' : '0');
  }, [dark]);

  useEffect(() => {
    document.documentElement.setAttribute('data-edition', edition);
    localStorage.setItem('amdaily_edition', edition);
    document.title = edition === 'am' ? 'The AM Daily' : 'The PM Daily';
  }, [edition]);

  useEffect(() => {
    document.documentElement.style.setProperty('--red', tweaks.accent);
    document.documentElement.style.setProperty('--font-scale', tweaks.scale);
    localStorage.setItem('amdaily_tweaks', JSON.stringify(tweaks));
    window.parent.postMessage({ type: '__edit_mode_set_keys', edits: tweaks }, '*');
  }, [tweaks]);

  useEffect(() => {
    const h = e => {
      if (e.data?.type === '__activate_edit_mode') setTweaksOn(true);
      if (e.data?.type === '__deactivate_edit_mode') setTweaksOn(false);
    };
    window.addEventListener('message', h);
    window.parent.postMessage({ type: '__edit_mode_available' }, '*');
    return () => window.removeEventListener('message', h);
  }, []);

  const showToast = (msg) => {
    setToast({ show: true, msg });
    setTimeout(() => setToast(t => ({ ...t, show: false })), 2500);
  };

  const doLoad = useCallback(async (force = false) => {
    if (!force) {
      const cached = loadArticleCache();
      if (cached) { setArticles(cached); }
    }

    const sources = window.loadSources ? window.loadSources() : [];
    setDataSource({ mode: 'live', meta: { source: `${sources.length} publishers` } });

    if (!sources.length || !window.fetchSource) {
      console.error('[The Daily] sources not loaded — hard refresh (⌘⇧R) to clear cache');
      return;
    }

    const results = await Promise.all(sources.map(window.fetchSource));
    const all = results.flat();

    // Dedupe by title prefix
    const seen = new Set();
    const deduped = all.filter(a => {
      const key = a.title.slice(0, 60).toLowerCase();
      if (seen.has(key)) return false;
      seen.add(key); return true;
    });

    deduped.sort((a, b) => (b.pubMs || 0) - (a.pubMs || 0));
    console.log(`[The Daily] Loaded ${deduped.length} articles from ${sources.length} sources`);

    // Round-robin interleave across publishers to prevent any single feed dominating
    const bySource = {};
    deduped.forEach(a => { (bySource[a.source] ||= []).push(a); });
    const sourceNames = Object.keys(bySource);
    const interleaved = [];
    let remaining = true;
    while (remaining) {
      remaining = false;
      for (const name of sourceNames) {
        const next = bySource[name].shift();
        if (next) { interleaved.push(next); remaining = true; }
      }
    }

    const bySection = { top: [], trending: [], deep: [], quick: [] };
    interleaved.forEach(a => { if (bySection[a.section]) bySection[a.section].push(a); });

    // Prioritize articles with thumbnails in hero/trending sections
    const preferWithImage = (list) => list.slice().sort((a, b) => {
      const ai = a.thumbnail ? 1 : 0, bi = b.thumbnail ? 1 : 0;
      if (ai !== bi) return bi - ai;
      return 0;
    });
    bySection.top      = preferWithImage(bySection.top);
    bySection.trending = preferWithImage(bySection.trending);

    // Cap per-source density: 1 per source in hero, 2 in other sections
    const rebalance = (list, maxPerSource) => {
      const counts = {};
      const kept = [], spill = [];
      for (const a of list) {
        const n = counts[a.source] || 0;
        if (n < maxPerSource) { kept.push(a); counts[a.source] = n + 1; }
        else spill.push(a);
      }
      return [kept, spill];
    };
    let spill;
    [bySection.top,      spill] = rebalance(bySection.top, 1);
    bySection.trending = [...bySection.trending, ...spill];
    [bySection.trending, spill] = rebalance(bySection.trending, 2);
    bySection.deep     = [...bySection.deep, ...spill];
    [bySection.deep,     spill] = rebalance(bySection.deep, 2);
    bySection.quick    = [...bySection.quick, ...spill];
    [bySection.quick,    spill] = rebalance(bySection.quick, 2);

    // Redistribute if any section is empty
    const sectionKeys = ['top', 'trending', 'deep', 'quick'];
    for (const s of sectionKeys) {
      if (bySection[s].length === 0) {
        const donor = sectionKeys.find(x => bySection[x].length > 3);
        if (donor) bySection[s].push(bySection[donor].pop());
      }
    }

    setArticles(bySection);
    saveArticleCache(bySection);
  }, []);

  useEffect(() => { doLoad(); }, []);

  // Auto-refresh at 6am / 6pm
  useEffect(() => {
    const check = () => {
      const now = new Date();
      if ((now.getHours() === 6 || now.getHours() === 18) && now.getMinutes() === 0) doLoad(true);
    };
    const id = setInterval(check, 60000);
    return () => clearInterval(id);
  }, [doLoad]);

  const handleBookmark = useCallback((guid) => {
    setBookmarks(prev => {
      const removing = prev.includes(guid);
      const next = removing ? prev.filter(id => id !== guid) : [...prev, guid];
      saveBookmarks(next);
      showToast(removing ? 'Removed' : '✓ Saved');
      return next;
    });
  }, []);

  const handleSummary = useCallback((id, text) => {
    setSummaries(prev => { const next = { ...prev, [id]: text }; saveSummaries(next); return next; });
  }, []);

  const handleRefresh = () => { showToast('Refreshing…'); doLoad(true); };

  const bProps = { bookmarks, onBookmark: handleBookmark };
  const sProps = { summaries, onSummary: handleSummary };

  return (
    <>
      <Toast msg={toast.msg} show={toast.show} />
      <Masthead
        edition={edition}
        onEdition={setEdition}
        dark={dark}
        onToggleDark={() => setDark(d => !d)}
        bookmarkCount={bookmarks.length}
        onRefresh={handleRefresh}
        dataSource={dataSource}
        topic={topic}
        onTopic={(k) => { setTopic(k); showToast(k ? `Filtered: ${window.TOPIC_GROUPS.find(g=>g.key===k)?.label}` : 'All topics'); }}
        onOpenSources={() => setSourcesPanelOpen(true)}
      />

      {(() => {
        const topicSources = topic ? window.sourcesForTopic(topic) : null;
        const filt = (arr) => topicSources ? arr.filter(a => topicSources.has(a.source)) : arr;
        const filtered = {
          top: filt(articles.top),
          trending: filt(articles.trending),
          deep: filt(articles.deep),
          quick: filt(articles.quick),
        };
        // If a section is empty after filtering, borrow from all filtered articles
        if (topicSources) {
          const all = [...filtered.top, ...filtered.trending, ...filtered.deep, ...filtered.quick];
          ['top','trending','deep','quick'].forEach(k => {
            if (!filtered[k].length) filtered[k] = all.slice(0, 3);
          });
        }
        return (
          <main className="shell">
            <div id="top-stories"><SectionHdr label="Top Stories" count="01" /><HeroSection articles={filtered.top} {...bProps} {...sProps} /></div>
            <div id="trending"><SectionHdr label="Trending" count="02" /><div style={{ paddingBottom: '72px', borderBottom: '1px solid var(--rule)' }}><TrendingSection articles={filtered.trending} {...bProps} /></div></div>
            <div id="deep-reads"><SectionHdr label="Deep Reads" count="03" /><div style={{ paddingBottom: '72px', borderBottom: '1px solid var(--rule)' }}><DeepReads articles={filtered.deep} {...bProps} {...sProps} /></div></div>
            <div id="quick-hits"><SectionHdr label="Quick Hits" count="04" /><QuickHits articles={filtered.quick} /></div>
          </main>
        );
      })()}

      <footer className="shell" style={{
        borderTop: '2px solid var(--ink)', marginTop: '40px',
        padding: '28px 40px 36px', textAlign: 'center',
        fontFamily: 'var(--font-mono)', fontSize: '10px', letterSpacing: '0.1em',
        textTransform: 'uppercase', color: 'var(--ink3)',
      }}>
        <div style={{ fontFamily: 'var(--font-display)', fontStyle: 'italic', fontSize: '22px', color: 'var(--ink)', letterSpacing: '-0.01em', textTransform: 'none', marginBottom: '14px' }}>The Daily</div>
        <div>Powered by Dia · {edition.toUpperCase()} Edition · {new Date().getFullYear()}</div>
        <div style={{ marginTop: '10px', fontSize: '9px', color: 'var(--rule-strong)' }}>
          Sources: Verge · MacRumors · Ars Technica · Pitchfork · Variety · WDWNT
        </div>
        <div style={{ marginTop: '6px', fontSize: '9px', color: 'var(--rule-strong)' }}>
          Auto-refreshes at 6:00 AM &amp; 6:00 PM
        </div>
      </footer>

      <TweaksPanel visible={tweaksOn} tweaks={tweaks} onChange={t => setTweaks(t)} onSourcesChange={() => doLoad(true)} />
      <SourcesPanel open={sourcesPanelOpen} onClose={() => setSourcesPanelOpen(false)} onSourcesChange={() => doLoad(true)} />
    </>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
