// Trove — School Calendar
// A browsable calendar of UK primary school events. Agenda + grid views,
// filters by type and key stage, and a detail drawer that links into the Wiki.
// Data: window.SCHOOL_CALENDAR (calendar-data.js).

const CAL_TYPES = {
  festival:      { label: 'Festival', color: 'oklch(0.58 0.13 320)' },
  awareness:     { label: 'Awareness day', color: 'oklch(0.56 0.11 245)' },
  seasonal:      { label: 'Seasonal & fun', color: 'oklch(0.56 0.11 150)' },
  national:      { label: 'National day', color: 'oklch(0.56 0.13 28)' },
  commemoration: { label: 'Commemoration', color: 'oklch(0.52 0.03 265)' },
  term:          { label: 'Term dates', color: 'oklch(0.62 0.10 72)' },
};
const CAL_YEARS = ['2025/26', '2026/27'];
// Academic-year month order: September (8) through August (7).
const CAL_MONTH_ORDER = [8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7];
const CAL_MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
const CAL_WD_SHORT = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const CAL_WD_LONG = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];

function calParseISO(s) { const [y, m, d] = s.split('-').map(Number); return new Date(y, m - 1, d); }
function calEvRange(ev, year) {
  const d = ev.d[year];
  if (!d) return null;
  if (Array.isArray(d)) return { start: calParseISO(d[0]), end: calParseISO(d[1]) };
  return { start: calParseISO(d), end: null };
}
function calLongDate(date) { return `${date.getDate()} ${CAL_MONTHS[date.getMonth()]} ${date.getFullYear()}`; }
function calFullWhen(r) {
  if (!r.end) return `${CAL_WD_LONG[r.start.getDay()]} ${calLongDate(r.start)}`;
  return `${calLongDate(r.start)} to ${calLongDate(r.end)}`;
}
function calCurrentAcademicYear() {
  const now = new Date();
  const y = now.getFullYear();
  const start = now.getMonth() >= 8 ? y : y - 1;
  const label = `${start}/${String(start + 1).slice(2)}`;
  return CAL_YEARS.includes(label) ? label : CAL_YEARS[0];
}
// Grid index (into CAL_MONTH_ORDER) of the current month, if today falls in this academic year.
function calDefaultGridIdx(year) {
  const now = new Date();
  const sy = parseInt(year.slice(0, 4), 10);
  const inYear = now >= new Date(sy, 8, 1) && now <= new Date(sy + 1, 7, 31, 23, 59, 59);
  if (!inYear) return 0;
  const pos = CAL_MONTH_ORDER.indexOf(now.getMonth());
  return pos < 0 ? 0 : pos;
}

// Next few real-world occurrences across every academic year in the data.
function calUpcoming(events, limit = 5) {
  const today = new Date(); today.setHours(0, 0, 0, 0);
  const horizon = new Date(today.getTime() + 28 * 86400000);
  const out = [];
  events.forEach(ev => {
    Object.keys(ev.d || {}).forEach(year => {
      const r = calEvRange(ev, year);
      if (!r) return;
      const s = new Date(r.start); s.setHours(0, 0, 0, 0);
      const e = new Date(r.end || r.start); e.setHours(0, 0, 0, 0);
      if (e >= today && s <= horizon) out.push({ ev, year, r, start: s, end: e });
    });
  });
  return out.sort((a, b) => a.start - b.start).slice(0, limit);
}
function calWhenLabel(o) {
  const today = new Date(); today.setHours(0, 0, 0, 0);
  if (today >= o.start && today <= o.end) return 'Today';
  const days = Math.round((o.start - today) / 86400000);
  if (days === 1) return 'Tomorrow';
  if (days < 7) return CAL_WD_LONG[o.start.getDay()];
  if (days < 14) return 'Next week';
  return `${o.start.getDate()} ${CAL_MONTHS[o.start.getMonth()].slice(0, 3)}`;
}

function CalendarScreen({ onOpenArticle, onOpenWiki, initialEventId }) {
  const { isMobile } = window.useViewport();
  const events = window.SCHOOL_CALENDAR || [];
  // If we arrived from a search result or a homepage card, open that event's
  // drawer on the academic year whose occurrence is still to come.
  const arrivedOn = React.useMemo(() => {
    if (!initialEventId) return null;
    const ev = events.find(e => e.id === initialEventId);
    if (!ev) return null;
    const today = new Date(); today.setHours(0, 0, 0, 0);
    const years = CAL_YEARS.filter(y => ev.d && ev.d[y]);
    if (!years.length) return null;
    const upcoming = years.find(y => {
      const r = calEvRange(ev, y);
      return r && (r.end || r.start) >= today;
    });
    return { id: ev.id, year: upcoming || years[years.length - 1] };
  }, [initialEventId, events]);

  const [year, setYear] = React.useState(() => (arrivedOn ? arrivedOn.year : calCurrentAcademicYear()));
  const [view, setView] = React.useState('agenda'); // agenda | grid
  const [types, setTypes] = React.useState(() => Object.fromEntries(Object.keys(CAL_TYPES).map(k => [k, true])));
  const [ks, setKs] = React.useState('all'); // all | KS1 | KS2
  const [selectedId, setSelectedId] = React.useState(arrivedOn ? arrivedOn.id : null);
  const [gridIdx, setGridIdx] = React.useState(() => calDefaultGridIdx(year)); // index into CAL_MONTH_ORDER

  const startYear = parseInt(year.slice(0, 4), 10);
  const allTypesOn = Object.values(types).every(Boolean);

  const withDates = React.useMemo(() => {
    return events
      .filter(ev => types[ev.type] && (ks === 'all' || (ev.ks || []).includes(ks)))
      .map(ev => ({ ev, r: calEvRange(ev, year) }))
      .filter(x => x.r)
      .sort((a, b) => a.r.start - b.r.start);
  }, [events, types, ks, year]);

  const selected = selectedId ? events.find(e => e.id === selectedId) : null;
  const selectedRange = selected ? calEvRange(selected, year) : null;

  function toggleType(k) { setTypes(t => ({ ...t, [k]: !t[k] })); }
  function setAllTypes(v) { setTypes(Object.fromEntries(Object.keys(CAL_TYPES).map(k => [k, v]))); }

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

  // Jump the grid to the current month when the user picks a year from the tab.
  function selectYear(y) { setYear(y); setGridIdx(calDefaultGridIdx(y)); }

  // Continuous month navigation across both academic years (Sept of year N rolls back to Aug of year N-1).
  const yearIdx = CAL_YEARS.indexOf(year);
  const canGridPrev = gridIdx > 0 || yearIdx > 0;
  const canGridNext = gridIdx < 11 || yearIdx < CAL_YEARS.length - 1;
  function gridPrev() {
    if (gridIdx > 0) setGridIdx(gridIdx - 1);
    else if (yearIdx > 0) { setYear(CAL_YEARS[yearIdx - 1]); setGridIdx(11); }
  }
  function gridNext() {
    if (gridIdx < 11) setGridIdx(gridIdx + 1);
    else if (yearIdx < CAL_YEARS.length - 1) { setYear(CAL_YEARS[yearIdx + 1]); setGridIdx(0); }
  }

  // Agenda: group by month, in academic order (already sorted chronologically).
  const groups = [];
  { let cur = null;
    withDates.forEach(({ ev, r }) => {
      const key = `${r.start.getFullYear()}-${r.start.getMonth()}`;
      if (!cur || cur.key !== key) { cur = { key, label: `${CAL_MONTHS[r.start.getMonth()]} ${r.start.getFullYear()}`, items: [] }; groups.push(cur); }
      cur.items.push({ ev, r });
    });
  }

  return (
    <main style={{ maxWidth: 1280, margin: '0 auto', padding: isMobile ? '36px 20px 70px' : '56px 48px 80px' }}>
      <div style={{ marginBottom: 28 }}>
        <div className="t-micro" style={{ color: 'var(--gold-deep)', marginBottom: 12 }}>Trove School Calendar</div>
        <h1 style={{ fontFamily: 'var(--font-serif)', fontWeight: 700, fontSize: 'clamp(34px, 4.2vw, 50px)', letterSpacing: '-.024em', lineHeight: 1.1, margin: '0 0 12px' }}>
          The primary school year, all in one place.
        </h1>
        <p style={{ fontSize: 17, color: 'var(--fg-muted)', lineHeight: 1.55, margin: 0 }}>
          Festivals, awareness days, commemorations and term dates for UK primary schools. Tap any event to learn more, with links into the Wiki.
        </p>
      </div>

      <CalUpcoming events={events} onPick={(o) => { setYear(o.year); setGridIdx(calDefaultGridIdx(o.year)); setSelectedId(o.ev.id); }} />

      {/* Controls: year + view */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap', marginBottom: 16 }}>
        <CalSegmented options={CAL_YEARS} value={year} onChange={selectYear} />
        <div style={{ marginLeft: 'auto', display: 'flex', gap: 6 }}>
          <CalViewBtn active={view === 'agenda'} onClick={() => setView('agenda')} icon="List" label="Agenda" />
          <CalViewBtn active={view === 'grid'} onClick={() => setView('grid')} icon="Grid" label="Grid" />
        </div>
      </div>

      {/* Filters */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap', marginBottom: 10 }}>
        <button onClick={() => setAllTypes(!allTypesOn)} className="pill" style={{ cursor: 'pointer', fontWeight: 600, borderColor: allTypesOn ? 'var(--border-strong)' : 'var(--border)' }}>
          {allTypesOn ? 'All types' : 'Select all'}
        </button>
        {Object.entries(CAL_TYPES).map(([k, meta]) => {
          const on = types[k];
          return (
            <button key={k} onClick={() => toggleType(k)} className="pill"
              style={{ cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 7, opacity: on ? 1 : 0.45,
                       borderColor: on ? meta.color : 'var(--border)', color: on ? 'var(--fg)' : 'var(--fg-muted)' }}>
              <span style={{ width: 9, height: 9, borderRadius: 999, background: meta.color }} />{meta.label}
            </button>
          );
        })}
        <div style={{ width: 1, height: 22, background: 'var(--border)', margin: '0 2px' }} />
        <CalSegmented options={['all', 'KS1', 'KS2']} labels={['All ages', 'KS1', 'KS2']} value={ks} onChange={setKs} small />
      </div>

      {types.term && (
        <div style={{ fontSize: 12.5, color: 'var(--fg-subtle)', marginBottom: 20, display: 'flex', alignItems: 'center', gap: 6 }}>
          <span style={{ color: CAL_TYPES.term.color, fontWeight: 700 }}>*</span>
          Term dates are a guide only and vary by local authority and school.
        </div>
      )}
      {!types.term && <div style={{ marginBottom: 20 }} />}

      {withDates.length === 0 ? (
        <div style={{ padding: '48px 28px', textAlign: 'center', background: 'var(--bg-sunken)', border: '1px solid var(--border)', borderRadius: 'var(--radius-lg)', color: 'var(--fg-muted)' }}>
          No events match these filters. Try turning a type back on.
        </div>
      ) : view === 'agenda' ? (
        <CalAgenda groups={groups} onSelect={setSelectedId} year={year} />
      ) : (
        <CalGrid startYear={startYear} gridIdx={gridIdx} items={withDates} onSelect={setSelectedId} onPrev={gridPrev} onNext={gridNext} canPrev={canGridPrev} canNext={canGridNext} />
      )}

      {selected && (
        <CalDrawer
          ev={selected} range={selectedRange}
          onClose={() => setSelectedId(null)}
          onOpenArticle={onOpenArticle}
        />
      )}
    </main>
  );
}

// ── Agenda view (collapsible months) ──────────────────────────
function CalAgenda({ groups, onSelect, year }) {
  // Default-open the current real-world month if it's in this academic year,
  // otherwise the first month that has events.
  const now = new Date();
  const nowKey = `${now.getFullYear()}-${now.getMonth()}`;
  const defaultKey = groups.some(g => g.key === nowKey) ? nowKey : (groups[0] && groups[0].key);

  const [open, setOpen] = React.useState({});
  // Reset open-state whenever the set of months changes (year/filter change).
  React.useEffect(() => {
    setOpen(defaultKey ? { [defaultKey]: true } : {});
  }, [year, defaultKey, groups.length]);

  const allOpen = groups.length > 0 && groups.every(g => open[g.key]);
  function toggle(key) { setOpen(o => ({ ...o, [key]: !o[key] })); }
  function setAll(v) { const next = {}; if (v) groups.forEach(g => next[g.key] = true); setOpen(next); }

  return (
    <div>
      <div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 12 }}>
        <button onClick={() => setAll(!allOpen)}
          style={{ background: 'transparent', border: 0, color: 'var(--gold-deep)', fontSize: 13, fontWeight: 600, cursor: 'pointer', padding: '6px 4px' }}>
          {allOpen ? 'Collapse all' : 'Expand all'}
        </button>
      </div>

      <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
        {groups.map(g => {
          const isOpen = !!open[g.key];
          const isNow = g.key === nowKey;
          return (
            <section key={g.key} style={{ border: '1px solid var(--border)', borderRadius: 'var(--radius-lg)', background: 'var(--surface)', overflow: 'hidden' }}>
              <button
                onClick={() => toggle(g.key)}
                aria-expanded={isOpen}
                style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 14, textAlign: 'left', padding: '16px 22px', background: 'transparent', border: 0, cursor: 'pointer' }}
                onMouseEnter={(e) => e.currentTarget.style.background = 'var(--bg-sunken)'}
                onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
                <div style={{ flex: 1, minWidth: 0, display: 'flex', alignItems: 'baseline', gap: 10, flexWrap: 'wrap' }}>
                  <h2 style={{ fontFamily: 'var(--font-serif)', fontWeight: 500, fontSize: 20, letterSpacing: '-.01em', margin: 0, whiteSpace: 'nowrap' }}>{g.label}</h2>
                  {isNow && (
                    <span className="pill" style={{ background: 'color-mix(in oklab, var(--gold) 22%, var(--surface))', borderColor: 'var(--gold)', color: 'var(--gold-deep)', fontWeight: 700, whiteSpace: 'nowrap' }}>This month</span>
                  )}
                  <span style={{ fontSize: 12.5, color: 'var(--fg-subtle)', fontWeight: 600, whiteSpace: 'nowrap' }}>{g.items.length} event{g.items.length === 1 ? '' : 's'}</span>
                </div>
                <Icon.ChevronDown width="20" height="20" style={{ color: 'var(--fg-muted)', flexShrink: 0, transition: 'transform .2s', transform: isOpen ? 'rotate(180deg)' : 'none' }} />
              </button>
              {isOpen && (
                <div style={{ padding: '6px 16px 16px', borderTop: '1px solid var(--border)' }}>
                  <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 12 }}>
                    {g.items.map(({ ev, r }) => {
                      const meta = CAL_TYPES[ev.type];
                      return (
                        <button key={ev.id} onClick={() => onSelect(ev.id)} className="card"
                          style={{ textAlign: 'left', cursor: 'pointer', background: 'var(--surface)', padding: '14px 18px',
                                   display: 'flex', alignItems: 'center', gap: 18 }}>
                          <div style={{ flexShrink: 0, width: 50, textAlign: 'center' }}>
                            <div style={{ fontSize: 11, color: 'var(--fg-subtle)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '.04em' }}>{CAL_WD_SHORT[r.start.getDay()]}</div>
                            <div style={{ fontFamily: 'var(--font-serif)', fontSize: 26, fontWeight: 500, lineHeight: 1, color: 'var(--fg)' }}>{r.start.getDate()}</div>
                            <div style={{ fontSize: 11, color: 'var(--fg-subtle)', fontWeight: 600 }}>{CAL_MONTHS[r.start.getMonth()].slice(0, 3)}</div>
                          </div>
                          <div style={{ width: 3, alignSelf: 'stretch', borderRadius: 3, background: meta.color, flexShrink: 0 }} />
                          <div style={{ flex: 1, minWidth: 0 }}>
                            <div style={{ fontSize: 15.5, fontWeight: 600, color: 'var(--fg)' }}>
                              {ev.title}{ev.type === 'term' ? <span style={{ color: meta.color }}> *</span> : null}
                            </div>
                            <div style={{ fontSize: 12.5, color: 'var(--fg-muted)', marginTop: 2, display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
                              <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
                                <span style={{ width: 7, height: 7, borderRadius: 999, background: meta.color }} />{meta.label}
                              </span>
                              {r.end && <span>· {calLongDate(r.start)} to {calLongDate(r.end)}</span>}
                            </div>
                          </div>
                          <Icon.ChevronRight width="16" height="16" style={{ color: 'var(--fg-subtle)', flexShrink: 0 }} />
                        </button>
                      );
                    })}
                  </div>
                </div>
              )}
            </section>
          );
        })}
      </div>
    </div>
  );
}

// ── Grid view ─────────────────────────────────────────────────
function CalGrid({ startYear, gridIdx, items, onSelect, onPrev, onNext, canPrev, canNext }) {
  const { isMobile } = window.useViewport();
  const monthIdx = CAL_MONTH_ORDER[gridIdx];
  const calYear = monthIdx >= 8 ? startYear : startYear + 1;
  const firstDow = (new Date(calYear, monthIdx, 1).getDay() + 6) % 7; // Monday-first offset
  const daysInMonth = new Date(calYear, monthIdx + 1, 0).getDate();

  // events active on each day of this month
  function eventsOnDay(day) {
    const cell = new Date(calYear, monthIdx, day).setHours(0, 0, 0, 0);
    return items.filter(({ r }) => {
      const s = new Date(r.start).setHours(0, 0, 0, 0);
      const e = (r.end ? new Date(r.end) : new Date(r.start)).setHours(0, 0, 0, 0);
      return cell >= s && cell <= e;
    });
  }

  const cells = [];
  for (let i = 0; i < firstDow; i++) cells.push(null);
  for (let d = 1; d <= daysInMonth; d++) cells.push(d);

  const monthHasEvents = items.some(({ r }) => {
    const inMonth = (dt) => dt.getFullYear() === calYear && dt.getMonth() === monthIdx;
    return inMonth(r.start) || (r.end && (inMonth(r.end) || (r.start < new Date(calYear, monthIdx, 1) && r.end > new Date(calYear, monthIdx + 1, 0))));
  });

  return (
    <div>
      {/* Month navigator */}
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}>
        <button onClick={onPrev} disabled={!canPrev}
          className="btn btn-sm" style={{ padding: 8, opacity: canPrev ? 1 : 0.4 }} aria-label="Previous month">
          <Icon.ChevronLeft width="18" height="18" />
        </button>
        <div style={{ fontFamily: 'var(--font-serif)', fontSize: 24, fontWeight: 500, letterSpacing: '-.01em', whiteSpace: 'nowrap' }}>
          {CAL_MONTHS[monthIdx]} {calYear}
        </div>
        <button onClick={onNext} disabled={!canNext}
          className="btn btn-sm" style={{ padding: 8, opacity: canNext ? 1 : 0.4 }} aria-label="Next month">
          <Icon.ChevronRight width="18" height="18" />
        </button>
      </div>

      <div style={{ border: '1px solid var(--border)', borderRadius: 'var(--radius-lg)', overflow: 'hidden', background: 'var(--surface)' }}>
        {/* Weekday header */}
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', borderBottom: '1px solid var(--border)', background: 'var(--bg-sunken)' }}>
          {['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].map(d => (
            <div key={d} style={{ padding: '10px 0', textAlign: 'center', fontSize: 11.5, fontWeight: 700, letterSpacing: '.05em', textTransform: 'uppercase', color: 'var(--fg-subtle)' }}>{d}</div>
          ))}
        </div>
        {/* Day cells */}
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)' }}>
          {cells.map((d, i) => {
            const evs = d ? eventsOnDay(d) : [];
            const isWeekend = (i % 7) >= 5;
            const now = new Date();
            const isToday = d && calYear === now.getFullYear() && monthIdx === now.getMonth() && d === now.getDate();
            return (
              <div key={i} style={{
                minHeight: isMobile ? 58 : 96, padding: isMobile ? 3 : 6, borderRight: (i % 7 !== 6) ? '1px solid var(--border)' : 0,
                borderTop: i >= 7 ? '1px solid var(--border)' : 0,
                background: isToday ? 'color-mix(in oklab, var(--gold) 12%, var(--surface))' : (d ? (isWeekend ? 'color-mix(in oklab, var(--bg-sunken) 50%, transparent)' : 'transparent') : 'var(--bg-sunken)'),
                display: 'flex', flexDirection: 'column', gap: 3,
              }}>
                {d && (
                  isToday
                    ? <div style={{ alignSelf: 'flex-start', display: 'flex', alignItems: 'center', justifyContent: 'center', width: 22, height: 22, borderRadius: 999, background: 'var(--gold-deep)', color: '#fff', fontSize: 12, fontWeight: 800, margin: '0 2px' }}>{d}</div>
                    : <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--fg-muted)', padding: '2px 4px' }}>{d}</div>
                )}
                {evs.slice(0, 3).map(({ ev }) => {
                  const meta = CAL_TYPES[ev.type];
                  return (
                    <button key={ev.id} onClick={() => onSelect(ev.id)} title={ev.title}
                      style={{ textAlign: 'left', cursor: 'pointer', border: 0, borderRadius: 5, padding: '3px 6px',
                               background: `color-mix(in oklab, ${meta.color} 15%, var(--surface))`,
                               color: 'var(--fg)', fontSize: 11, fontWeight: 600, lineHeight: 1.2,
                               whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
                               borderLeft: `2px solid ${meta.color}` }}>
                      {ev.title}
                    </button>
                  );
                })}
                {evs.length > 3 && <div style={{ fontSize: 10.5, color: 'var(--fg-subtle)', padding: '0 6px', fontWeight: 600 }}>+{evs.length - 3} more</div>}
              </div>
            );
          })}
        </div>
      </div>
      {!monthHasEvents && (
        <div style={{ marginTop: 14, fontSize: 13, color: 'var(--fg-subtle)', textAlign: 'center' }}>
          No events this month with the current filters. Use the arrows to browse other months.
        </div>
      )}
    </div>
  );
}

// ── Detail drawer ─────────────────────────────────────────────
function CalDrawer({ ev, range, onClose, onOpenArticle }) {
  const meta = CAL_TYPES[ev.type];
  React.useEffect(() => { document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = ''; }; }, []);
  const hasArticle = !!ev.wiki && (window.WIKI_ARTICLES || []).some(a => a.slug === ev.wiki);

  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, background: 'rgba(15,26,48,.45)', backdropFilter: 'blur(3px)', zIndex: 200, display: 'flex', justifyContent: 'flex-end' }}>
      <div onClick={(e) => e.stopPropagation()} className="cal-drawer"
        style={{ width: 'min(440px, 100%)', height: '100%', background: 'var(--bg)', borderLeft: '1px solid var(--border)',
                 boxShadow: 'var(--shadow-xl)', overflowY: 'auto', padding: '24px 28px 40px' }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 22 }}>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 7, fontSize: 12.5, fontWeight: 700, color: meta.color, textTransform: 'uppercase', letterSpacing: '.05em' }}>
            <span style={{ width: 9, height: 9, borderRadius: 999, background: meta.color }} />{meta.label}
          </span>
          <button onClick={onClose} className="btn btn-sm" style={{ padding: 6 }} aria-label="Close"><Icon.X width="16" height="16" /></button>
        </div>

        <h2 style={{ fontFamily: 'var(--font-serif)', fontWeight: 500, fontSize: 30, letterSpacing: '-.018em', lineHeight: 1.12, margin: '0 0 10px' }}>{ev.title}</h2>

        <div style={{ display: 'inline-flex', alignItems: 'center', gap: 8, fontSize: 14, color: 'var(--fg)', fontWeight: 600, marginBottom: 6 }}>
          <Icon.Calendar width="16" height="16" style={{ color: 'var(--fg-muted)' }} />{calFullWhen(range)}
        </div>
        <div style={{ display: 'flex', gap: 6, marginBottom: 20 }}>
          {(ev.ks || []).map(k => <span key={k} className={`pill ${k === 'KS1' ? 'pill-ks1' : 'pill-ks2'}`}>{k}</span>)}
        </div>

        <p style={{ fontSize: 15.5, lineHeight: 1.6, color: 'var(--fg)', margin: '0 0 18px', textWrap: 'pretty' }}>{ev.blurb}</p>

        {ev.note && (
          <div style={{ display: 'flex', gap: 10, padding: '12px 14px', background: 'var(--bg-sunken)', border: '1px solid var(--border)', borderRadius: 'var(--radius)', marginBottom: 20 }}>
            <span style={{ color: meta.color, fontWeight: 700, flexShrink: 0 }}>*</span>
            <span style={{ fontSize: 13, color: 'var(--fg-muted)', lineHeight: 1.5 }}>{ev.note}</span>
          </div>
        )}

        {hasArticle ? (
          <button onClick={() => onOpenArticle(ev.wiki)} className="btn btn-primary btn-lg" style={{ width: '100%' }}>
            Read more in the Wiki<Icon.ArrowRight width="15" height="15" />
          </button>
        ) : (
          <div style={{ fontSize: 13, color: 'var(--fg-subtle)', lineHeight: 1.5, padding: '12px 0', borderTop: '1px solid var(--border)' }}>
            No Wiki article for this one yet.
          </div>
        )}
      </div>
    </div>
  );
}

// ── Upcoming strip (top of the page) ──────────────────────────
function CalUpcoming({ events, onPick }) {
  const { isMobile } = window.useViewport();
  const items = React.useMemo(() => calUpcoming(events), [events]);
  if (!items.length) return null;
  const soon = items.some(o => (o.start - new Date().setHours(0, 0, 0, 0)) / 86400000 < 7);

  return (
    <div style={{ marginBottom: 26 }}>
      <div className="t-micro" style={{ color: 'var(--gold-deep)', marginBottom: 10 }}>
        {soon ? 'Events this week' : 'Coming up next'}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : `repeat(${Math.min(items.length, 5)}, 1fr)`, gap: 10 }}>
        {items.map(o => {
          const meta = CAL_TYPES[o.ev.type];
          const when = calWhenLabel(o);
          const isToday = when === 'Today';
          return (
            <button key={`${o.ev.id}-${o.year}`} onClick={() => onPick(o)} className="card"
              style={{
                background: isToday ? 'color-mix(in oklab, var(--accent) 10%, var(--surface))' : 'var(--surface)',
                borderColor: isToday ? 'var(--accent)' : 'var(--border)',
                padding: '13px 15px', textAlign: 'left', cursor: 'pointer',
                display: 'flex', flexDirection: 'column', gap: 6, height: '100%',
              }}>
              <span style={{
                fontSize: 11.5, fontWeight: 700, letterSpacing: '.04em', textTransform: 'uppercase',
                color: isToday ? 'var(--gold-deep)' : 'var(--fg-subtle)',
              }}>{when}</span>
              <span style={{ fontSize: 14.5, fontWeight: 700, color: 'var(--fg)', lineHeight: 1.3, textWrap: 'pretty' }}>{o.ev.title}</span>
              <span style={{ marginTop: 'auto', display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12, color: 'var(--fg-muted)', fontWeight: 600 }}>
                <span style={{ width: 8, height: 8, borderRadius: 999, background: meta.color, flexShrink: 0 }} />{meta.label}
              </span>
            </button>
          );
        })}
      </div>
    </div>
  );
}

// ── Small shared controls ─────────────────────────────────────
function CalSegmented({ options, labels, value, onChange, small }) {
  return (
    <div style={{ display: 'inline-flex', background: 'var(--bg-sunken)', border: '1px solid var(--border)', borderRadius: 10, padding: 3, gap: 2 }}>
      {options.map((opt, i) => {
        const on = opt === value;
        return (
          <button key={opt} onClick={() => onChange(opt)}
            style={{ border: 0, cursor: 'pointer', borderRadius: 7, padding: small ? '6px 12px' : '8px 16px',
                     fontSize: small ? 12.5 : 13.5, fontWeight: 600,
                     background: on ? 'var(--surface)' : 'transparent',
                     color: on ? 'var(--fg)' : 'var(--fg-muted)',
                     boxShadow: on ? 'var(--shadow-sm)' : 'none' }}>
            {labels ? labels[i] : opt}
          </button>
        );
      })}
    </div>
  );
}

function CalViewBtn({ active, onClick, icon, label }) {
  const I = Icon[icon];
  return (
    <button onClick={onClick}
      style={{ display: 'inline-flex', alignItems: 'center', gap: 7, cursor: 'pointer',
               border: `1px solid ${active ? 'var(--border-strong)' : 'var(--border)'}`, borderRadius: 9, padding: '8px 14px',
               background: active ? 'var(--surface)' : 'transparent', color: active ? 'var(--fg)' : 'var(--fg-muted)',
               fontSize: 13.5, fontWeight: 600, boxShadow: active ? 'var(--shadow-sm)' : 'none' }}>
      <I width="16" height="16" />{label}
    </button>
  );
}

window.CalendarScreen = CalendarScreen;
