// Broadsheet sections — part B: signup, cadence, send-days, topics, synthesis, explorer, footer

function Signup() {
  const { signup } = window.BS_DATA;
  return (
    <section className="section" id="signup">
      <div className="shell">
        <SectionHead num="08" tag="Signup friction"
        title="Publishers ask readers to commit before experiencing the product."
        lede="Most publishers require account registration; three in four use double opt-in. At the same time, nearly half show no content sample, and over 40% don't specify a delivery day at the point of signup." />
        <div className="bars">
          {signup.map(([l, s, p, accent]) =>
          <div key={l} className={`bar-row${accent ? ' is-accent' : ''}`}>
              <div className="label">{l}<small>{s}</small></div>
              <div className="bar"><span style={{ width: `${p}%` }} /></div>
              <div className="pct">{p}%</div>
            </div>
          )}
        </div>
        <div className="chart-foot">
          <div />
          <p>The pattern compounds: high entry requirements with low pre-signup transparency. Readers
            are asked to register, confirm, and wait, without seeing what they're waiting for.</p>
        </div>
      </div>
    </section>);

}

function Cadence() {
  const { cadence } = window.BS_DATA;
  return (
    <section className="section" id="cadence">
      <div className="shell">
        <SectionHead num="05" tag="Cadence"
        title="Weekly is the industry default, by a wide margin."
        lede="Share of newsletters by publishing cadence (n=859). High-frequency publishing is concentrated in a small subset of flagship daily briefings and weekday-only formats; the long tail of irregular and unknown cadences is grouped under Other." />
        <div className="cadence-grid">
          <div className="cadence-bars">
            {(() => {
              const maxH = Math.max(...cadence.map(([,, h]) => h));
              return cadence.map(([l, p, h]) =>
              <div key={l} className={`cadence-col${l === 'Weekly' ? ' is-peak' : ''}`}>
                  <div className="col-num">{p}%</div>
                  <div className="col-bar" style={{ height: `${h / maxH * 100}%` }} />
                  <div className="col-label">{l}</div>
                </div>
              );
            })()}
          </div>
          <aside className="cadence-callout">
            <div className="num">56%</div>
            <p>of all newsletter products in the sample publish weekly. Daily briefings and weekday-only
              (5×/week) formats together account for another 16% and are concentrated in a small group
              of flagship publishers.</p>
          </aside>
        </div>
      </div>
    </section>);

}

function SendDays() {
  const { heatmap } = window.BS_DATA;
  const allValues = heatmap.rows.flatMap((r) => r[1]).filter((v) => v > 0);
  const max = Math.max(...allValues);
  const colorFor = (v) => {
    if (v === 0) return null;
    const t = v / max;
    return `color-mix(in srgb, var(--accent) ${Math.round(t * 100)}%, var(--paper-2))`;
  };
  return (
    <section className="section" id="send-days">
      <div className="shell">
        <SectionHead num="06" tag="Send days"
        title="Sending peaks at the end of the week."
        lede="Friday concentrates more send activity than any other day across the sample (252 sends). The pattern holds across cadences and points to where there may be more room for new entrants. Saturday and Sunday remain the lightest send windows."
        note="Send-day signals, not unique newsletters. A daily Mon–Sun newsletter counts seven times (once per listed delivery day), so totals here exceed the 859-newsletter sample by design." />
        <div className="heatmap-wrap">
        <div className="heatmap">
          <div />
          {heatmap.days.map((d) => <div key={d} className="day-label">{d}</div>)}
          {heatmap.rows.map(([label, vals]) => {
              return (
                <React.Fragment key={label}>
                <div className="row-label">{label}</div>
                {vals.map((v, i) =>
                  <div key={i}
                  className={`cell${v === 0 ? ' is-empty' : ''}`}
                  style={v > 0 ? { background: colorFor(v), color: v > max * 0.55 ? 'var(--paper)' : 'var(--ink)' } : {}}
                  title={`${label} · ${heatmap.days[i]}: ${v}`}>
                    {v > 0 ? v : '—'}
                  </div>
                  )}
              </React.Fragment>);

            })}
        </div>
        </div>
        <div className="chart-foot">
          <div />
          <p>Friday-heavy weekly sends suggest publishers cluster around the end-of-week reading habit.
            Weekday-only (5×/week) formats are now a distinct cluster of 66 newsletters — they mirror
            the daily pattern Monday–Friday but vanish on weekends.</p>
        </div>
      </div>
    </section>);

}

function Topics() {
  const { topics } = window.BS_DATA;
  const max = Math.max(...topics.map((t) => t[1]));
  return (
    <section className="section" id="topics">
      <div className="shell">
        <SectionHead num="07" tag="Topics"
        title="Hard-news products lead the landscape."
        lede="Topic concentration shows where enterprise publishers are most likely to package newsletters as recurring editorial products. Politics, business, and general news dominate; standalone newsletters in lifestyle and verticals are still rare." />
        <div className="topic-list">
          {topics.map(([name, count, pct, top], i) =>
          <div key={name} className={`topic-row${top ? ' is-top' : ''}`}>
              <span className="rank">{String(i + 1).padStart(2, '0')}</span>
              <span className="name">{name}</span>
              <div className="bar"><span style={{ width: `${count / max * 100}%` }} /></div>
              <span className="count">{count}</span>
              <span className="pct">{pct}%</span>
            </div>
          )}
        </div>
      </div>
    </section>);

}

function Synthesis() {
  return (
    <section className="synthesis" id="synthesis">
      <div className="shell">
        <div className="section-tag"><em>09</em>Synthesis</div>
        <div>
          <blockquote>
            Newsletters are <em>widely deployed</em>, but <em>rarely developed</em> as products.
            That points to a larger direct-channel opportunity at a moment when audience
            relationships through search and social are increasingly unstable.
          </blockquote>
          <div className="synthesis-body">
            <p>Across the sample, newsletters function as supporting infrastructure for
              distribution, retention, and data capture, not as distinct editorial or
              commercial products. Hidden discovery surfaces, account-required signup,
              and missing pre-signup product information all compound to make newsletters
              feel like a side feature.</p>
            <p>At the same time, smaller creators and lean editorial teams are proving
              that high-value newsletter products can build habit, community, and
              willingness to pay. For enterprise publishers, that's both a benchmark
              and an invitation: treat newsletters as products, and the channel is
              still wide open.</p>
          </div>
        </div>
      </div>
    </section>);

}

function Explorer() {
  const FULL = window.NL2026_DATA;
  const rows = React.useMemo(() => FULL ? FULL.newsletters.map((n) => ({
    id: n.id,
    name: n.newsletterNameEnglish || n.newsletterNameOriginal || '—',
    nameOriginal: n.newsletterNameOriginal,
    publisher: n.publisher,
    country: n.country,
    region: n.region,
    topic: n.topic || '—',
    cadence: n.cadenceFrequency || n.cadenceEnglish || '—',
    cadenceOriginal: n.cadenceOriginal,
    cadenceEnglish: n.cadenceEnglish,
    access: n.accessModel || '—',
    signup: n.signupRequirement || '—',
    sample: n.sampleAvailable,
    days: n.deliveryDaysRaw,
    deliveryDays: n.deliveryDays || [],
    description: n.descriptionEnglish || '',
    previewUrl: n.previewUrl || ''
  })) : [], [FULL]);

  const publishersIndex = React.useMemo(() => {
    if (!FULL) return {};
    return Object.fromEntries(FULL.publishers.map((p) => [p.publisher, p]));
  }, [FULL]);

  const [q, setQ] = React.useState('');
  const [country, setCountry] = React.useState('');
  const [publisher, setPublisher] = React.useState('');
  const [topic, setTopic] = React.useState('');
  const [access, setAccess] = React.useState('');
  const [cadence, setCadence] = React.useState('');
  const [signup, setSignup] = React.useState('');
  const [sortKey, setSortKey] = React.useState('publisher');
  const [sortDir, setSortDir] = React.useState('asc');
  const [page, setPage] = React.useState(0);
  const [selected, setSelected] = React.useState(null);
  const PAGE_SIZE = 25;

  React.useEffect(() => {
    if (!selected) return;
    const onKey = (e) => {if (e.key === 'Escape') setSelected(null);};
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [selected]);

  const countries = React.useMemo(() => [...new Set(rows.map((r) => r.country))].sort(), [rows]);
  const publishers = React.useMemo(() => [...new Set(rows.map((r) => r.publisher))].sort(), [rows]);
  const topics = React.useMemo(() => [...new Set(rows.map((r) => r.topic))].sort(), [rows]);
  const cadences = React.useMemo(() => [...new Set(rows.map((r) => r.cadence))].sort(), [rows]);
  const accesses = React.useMemo(() => [...new Set(rows.map((r) => r.access))].sort(), [rows]);
  const signups = React.useMemo(() => [...new Set(rows.map((r) => r.signup))].sort(), [rows]);

  const filtered = React.useMemo(() => {
    const ql = q.toLowerCase();
    return rows.filter((r) => {
      if (q && !`${r.name} ${r.nameOriginal || ''} ${r.publisher} ${r.topic} ${r.description}`.toLowerCase().includes(ql)) return false;
      if (country && r.country !== country) return false;
      if (publisher && r.publisher !== publisher) return false;
      if (topic && r.topic !== topic) return false;
      if (cadence && r.cadence !== cadence) return false;
      if (access && r.access !== access) return false;
      if (signup && r.signup !== signup) return false;
      return true;
    });
  }, [rows, q, country, publisher, topic, cadence, access, signup]);

  const sorted = React.useMemo(() => {
    const out = [...filtered];
    out.sort((a, b) => {
      const av = (a[sortKey] || '').toString().toLowerCase();
      const bv = (b[sortKey] || '').toString().toLowerCase();
      return sortDir === 'asc' ? av.localeCompare(bv) : bv.localeCompare(av);
    });
    return out;
  }, [filtered, sortKey, sortDir]);

  React.useEffect(() => {setPage(0);}, [q, country, publisher, topic, cadence, access, signup]);

  const totalPages = Math.max(1, Math.ceil(sorted.length / PAGE_SIZE));
  const pageStart = page * PAGE_SIZE;
  const pageRows = sorted.slice(pageStart, pageStart + PAGE_SIZE);

  const sortHeader = (key, label) =>
  <th onClick={() => {
    if (sortKey === key) setSortDir(sortDir === 'asc' ? 'desc' : 'asc');else
    {setSortKey(key);setSortDir('asc');}
  }} className={`sort${sortKey === key ? ' is-active ' + sortDir : ''}`}>
      {label}<span className="sort-ind">{sortKey === key ? sortDir === 'asc' ? '↑' : '↓' : ''}</span>
    </th>;


  const reset = () => {
    setQ('');setCountry('');setPublisher('');setTopic('');
    setAccess('');setCadence('');setSignup('');
  };

  if (!FULL) {
    return (
      <section className="explorer" id="explorer"><div className="shell">
        <SectionHead num="10" tag="Data explorer" title="Loading dataset…" />
      </div></section>);

  }

  return (
    <section className="explorer" id="explorer">
      <div className="shell">
        <SectionHead num="10" tag="Data explorer"
        title="Inspect every newsletter in the sample."
        lede={`All ${rows.length} newsletters across ${publishers.length} publishers and ${countries.length} markets. Filter, sort, and search the full dataset, and click on any newsletter for more details. Newsletter titles and descriptions have been machine-translated into English.`} />
        <div className="explorer-controls">
          <input placeholder="Search newsletter, publisher, topic, description…" value={q} onChange={(e) => setQ(e.target.value)} />
          <select value={publisher} onChange={(e) => setPublisher(e.target.value)}>
            <option value="">All publishers</option>{publishers.map((c) => <option key={c}>{c}</option>)}
          </select>
          <select value={country} onChange={(e) => setCountry(e.target.value)}>
            <option value="">All countries</option>{countries.map((c) => <option key={c}>{c}</option>)}
          </select>
          <select value={topic} onChange={(e) => setTopic(e.target.value)}>
            <option value="">All topics</option>{topics.map((c) => <option key={c}>{c}</option>)}
          </select>
          <select value={cadence} onChange={(e) => setCadence(e.target.value)}>
            <option value="">Any cadence</option>{cadences.map((c) => <option key={c}>{c}</option>)}
          </select>
          <select value={access} onChange={(e) => setAccess(e.target.value)}>
            <option value="">Any access</option>{accesses.map((c) => <option key={c}>{c}</option>)}
          </select>
          <select value={signup} onChange={(e) => setSignup(e.target.value)}>
            <option value="">Any signup</option>{signups.map((c) => <option key={c}>{c}</option>)}
          </select>
        </div>
        <div className="explorer-meta">
          <span>Showing <b>{sorted.length === rows.length ? sorted.length : `${sorted.length} of ${rows.length}`}</b> newsletters
            {totalPages > 1 && <> · page <b>{page + 1}</b> of <b>{totalPages}</b></>}</span>
          <span className="explorer-meta-right">
            <button className="exp-reset" onClick={reset} type="button">Reset filters</button>
            <span>Source · CSM Newsletter Audit 2026</span>
          </span>
        </div>
        <div className="exp-table-wrap">
          <table className="exp-table">
            <thead>
              <tr>
                {sortHeader('name', 'Newsletter')}
                {sortHeader('publisher', 'Publisher')}
                {sortHeader('country', 'Country')}
                {sortHeader('topic', 'Topic')}
                {sortHeader('cadence', 'Cadence')}
                {sortHeader('access', 'Access')}
                {sortHeader('signup', 'Signup')}
              </tr>
            </thead>
            <tbody>
              {pageRows.length === 0 &&
              <tr><td colSpan="7" className="exp-empty">No newsletters match these filters.</td></tr>
              }
              {pageRows.map((r, i) =>
              <tr key={pageStart + i} onClick={() => setSelected(r)} className="exp-row" tabIndex={0}
              onKeyDown={(e) => {if (e.key === 'Enter' || e.key === ' ') {e.preventDefault();setSelected(r);}}}>
                  <td className="nl-name">
                    {r.name}
                    {r.nameOriginal && r.nameOriginal !== r.name && <small>{r.nameOriginal}</small>}
                  </td>
                  <td>{r.publisher}</td>
                  <td>{r.country}</td>
                  <td>{r.topic}</td>
                  <td>{r.cadence}</td>
                  <td><span className={`tag ${r.access === 'Free' ? 'is-free' : 'is-paid'}`}>{r.access}</span></td>
                  <td><span className="tag">{r.signup}</span></td>
                </tr>
              )}
            </tbody>
          </table>
        </div>
        {totalPages > 1 &&
        <div className="exp-pager">
            <button onClick={() => setPage(0)} disabled={page === 0}>« First</button>
            <button onClick={() => setPage((p) => Math.max(0, p - 1))} disabled={page === 0}>‹ Prev</button>
            <span className="exp-pager-state">Page {page + 1} / {totalPages}</span>
            <button onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))} disabled={page >= totalPages - 1}>Next ›</button>
            <button onClick={() => setPage(totalPages - 1)} disabled={page >= totalPages - 1}>Last »</button>
          </div>
        }
      </div>
      <NewsletterDrawer row={selected} publisher={selected && publishersIndex[selected.publisher]} onClose={() => setSelected(null)} />
    </section>);

}

function NewsletterDrawer({ row, publisher, onClose }) {
  const DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
  const DAY_FULL = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
  const open = !!row;

  // Lock body scroll while open
  React.useEffect(() => {
    if (!open) return;
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    return () => {document.body.style.overflow = prev;};
  }, [open]);

  const sendsOn = (day) => row && row.deliveryDays && row.deliveryDays.includes(day);
  const sendCount = row ? (row.deliveryDays || []).length : 0;

  return ReactDOM.createPortal(
    <div className={`drawer-root${open ? ' is-open' : ''}`} aria-hidden={!open}>
      <div className="drawer-backdrop" onClick={onClose} />
      <aside className="drawer" role="dialog" aria-modal="true"
      aria-label={row ? `${row.name} \u2014 newsletter details` : 'Newsletter details'}>
        {row &&
        <>
            <header className="drawer-head">
              <div className="drawer-eyebrow">
                <span className="drawer-tag">{row.region} · {row.country}</span>
                <button className="drawer-close" onClick={onClose} aria-label="Close">✕</button>
              </div>
              <h3 className="drawer-title">{row.name}</h3>
              {row.nameOriginal && row.nameOriginal !== row.name &&
            <div className="drawer-name-original">{row.nameOriginal}</div>
            }
              <div className="drawer-publisher">
                <span className="drawer-publisher-label">Published by</span>
                <b>{row.publisher}</b>
              </div>
            </header>

            {row.description &&
          <section className="drawer-section drawer-desc">
                <p>{row.description}</p>
              </section>
          }

            <section className="drawer-section">
              <div className="drawer-grid">
                <div>
                  <div className="drawer-key">Topic</div>
                  <div className="drawer-val">{row.topic}</div>
                </div>
                <div>
                  <div className="drawer-key">Cadence</div>
                  <div className="drawer-val">{row.cadence}</div>
                </div>
                <div>
                  <div className="drawer-key">Access</div>
                  <div className="drawer-val">
                    <span className={`tag ${row.access === 'Free' ? 'is-free' : 'is-paid'}`}>{row.access}</span>
                  </div>
                </div>
                <div>
                  <div className="drawer-key">Signup</div>
                  <div className="drawer-val"><span className="tag">{row.signup}</span></div>
                </div>
                <div>
                  <div className="drawer-key">Sample available</div>
                  <div className="drawer-val">{row.sample || '\u2014'}</div>
                </div>
                <div>
                  <div className="drawer-key">Sends per week</div>
                  <div className="drawer-val">{sendCount > 0 ? sendCount : '\u2014'}</div>
                </div>
              </div>
            </section>

            <section className="drawer-section">
              <div className="drawer-key drawer-key-block">Delivery days</div>
              {sendCount > 0 ?
            <div className="drawer-days">
                  {DAYS.map((d, i) =>
              <span key={d} className={`drawer-day${sendsOn(DAY_FULL[i]) ? ' is-on' : ''}`}>{d[0]}</span>
              )}
                </div> :

            <div className="drawer-sub">{row.days || 'Not specified'}</div>
            }
            </section>

            {publisher &&
          <section className="drawer-section drawer-pub">
                <div className="drawer-key drawer-key-block">Publisher context</div>
                <dl className="drawer-dl">
                  <div><dt>Homepage placement</dt><dd>{publisher.homepagePlacement || '\u2014'}</dd></div>
                  <div><dt>In hamburger menu</dt><dd>{publisher.hamburgerMenu || '\u2014'}</dd></div>
                  <div><dt>Signup model</dt><dd>{publisher.signupModel || '\u2014'}</dd></div>
                  <div><dt>Double opt-in</dt><dd>{publisher.doi || '\u2014'}</dd></div>
                  <div><dt>Consent</dt><dd>{publisher.consent || '\u2014'}</dd></div>
                  <div><dt>Newsletters in audit</dt><dd>{publisher.newsletterCount}</dd></div>
                </dl>
                {publisher.landingPage &&
            <a className="drawer-link" href={publisher.landingPage} target="_blank" rel="noopener">
                    Visit newsletter landing page →
                  </a>
            }
              </section>
          }

            <footer className="drawer-foot">
              <span>ID · {row.id}</span>
              <span>Press <kbd>Esc</kbd> to close</span>
            </footer>
          </>
        }
      </aside>
    </div>,
    document.body);
}

function Footer() {
  return (
    <footer className="site-footer">
      <div className="shell">
        <div>
          <div className="eyebrow" style={{ marginBottom: 16 }}>Center for Sustainable Media</div>
          <h3>2026 European Enterprise Publisher Newsletter Report</h3>
        </div>
        <div>
          <div className="section-tag" style={{ marginBottom: 14 }}>Read</div>
          <ul>
            <li><a href="#findings">Why newsletters matter</a></li>
            <li><a href="#discoverability">Discoverability</a></li>
            <li><a href="#access">Access model</a></li>
            <li><a href="#signup">Signup friction</a></li>
            <li><a href="#cadence">Cadence & send days</a></li>
            <li><a href="#explorer">Data explorer</a></li>
          </ul>
        </div>
        <div>
          <div className="section-tag" style={{ marginBottom: 14 }}>Method</div>
          <p>The audit captures the pre-signup product experience at the time the source dataset was prepared.
            Publisher websites and signup flows can change frequently, individual examples should be
            rechecked before being cited.</p>
          <p style={{ marginTop: 14, fontFamily: 'JetBrains Mono, monospace', fontSize: 11, letterSpacing: '0.04em' }}>CSM Reports №01 · 2026

          </p>
        </div>
      </div>
    </footer>);

}

Object.assign(window, { Signup, Cadence, SendDays, Topics, Synthesis, Explorer, NewsletterDrawer, Footer });