// ---------- Kursplan ----------
// Monthly calendar plotting course events. Uses the same filter set as the
// Alle-Kurse page (category chips, leader select, free-text search). The
// occurrence data is placeholder — designed so a backend / Google Calendar
// feed can drop in via window.WB_KURSPLAN_EVENTS later (see fallback below).

const { useState: useStateK, useMemo: useMemoK, useEffect: useEffectK } = React;

// Backend-URL für Live-Daten (Kursplan).
const WB_API_BASE_KP = 'https://project-zd7wa.vercel.app';

// localStorage-Cache („stale-while-revalidate"): Frontend rendert beim Mount
// sofort aus dem Cache (gefühlt: hardcoded, kein Spinner). Im Hintergrund
// läuft der fetch und ersetzt die Daten, wenn frisch. Hardcoded-Fallback
// greift nur beim allerersten Besuch ohne Cache UND ohne Netz.
const KP_CACHE_KEY = 'wb_kursplan_cache_v6';
const KP_CACHE_MAX_AGE_MS = 1000 * 60 * 60 * 24 * 30; // 30 Tage harte Grenze

const kpReadCache = () => {
  try {
    const raw = typeof localStorage !== 'undefined' ? localStorage.getItem(KP_CACHE_KEY) : null;
    if (!raw) return null;
    const parsed = JSON.parse(raw);
    if (!parsed || !parsed.ts || !parsed.data) return null;
    if (Date.now() - parsed.ts > KP_CACHE_MAX_AGE_MS) return null;
    return parsed.data;
  } catch { return null; }
};

const kpWriteCache = (data) => {
  try {
    if (typeof localStorage !== 'undefined') {
      localStorage.setItem(KP_CACHE_KEY, JSON.stringify({ data, ts: Date.now() }));
    }
  } catch { /* QuotaExceeded etc. — ignorieren */ }
};

// Wir behalten die Referenz auf die hardcoded Arrays — fetchen Daten async und
// MUTIEREN die Arrays in-place (splice + push), damit kpCats/kpCourses/kpLeaders
// weiterhin gelten. kpCatById/kpCourseById sind `let`, damit sie nach dem
// Fetch neu aufgebaut werden können (Closure-Lookups sehen dann die neue Map).
const kpCats = window.WB_CATEGORIES;
const kpCourses = window.WB_COURSES;
const kpLeaders = window.WB_LEADERS;
let kpCatById = Object.fromEntries(kpCats.map(c => [c.id, c]));
let kpCourseById = Object.fromEntries(kpCourses.map(c => [c.id, c]));

// Re-bauen nach Mutation. Wird nach Cache-Apply oder Fetch aufgerufen.
const kpRebuildIndexes = () => {
  kpCatById = Object.fromEntries(kpCats.map(c => [c.id, c]));
  kpCourseById = Object.fromEntries(kpCourses.map(c => [c.id, c]));
};

// Daten aus einem Cache-/API-Payload in window.WB_* übernehmen.
// `data` hat die gleiche Shape wie die /api/public/kursplan-Response.
const kpApplyData = (data) => {
  if (!data || !Array.isArray(data.categories)) return false;
  kpCats.length = 0; kpCats.push(...data.categories);
  kpCourses.length = 0; kpCourses.push(...data.courses);
  kpLeaders.length = 0; kpLeaders.push(...data.leaders);
  window.WB_KURSPLAN_EVENTS = (data.events ?? []).map((e) => ({
    ...e,
    date: typeof e.date === 'string' ? new Date(e.date + 'T00:00:00') : new Date(e.date),
  }));
  kpRebuildIndexes();
  return true;
};

// Synchroner Cache-Apply VOR React-Mount. So sieht der allererste Render
// (bei vorhandenem Cache) bereits die echten Daten — kein Hardcoded-Sprung.
(() => {
  const cached = kpReadCache();
  if (cached) kpApplyData(cached);
})();

// ---------- Date helpers ----------
const KP_MONTHS = ['Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'];
const KP_DOW = ['Mo','Di','Mi','Do','Fr','Sa','So'];
const kpPad = n => String(n).padStart(2, '0');
const kpKey = d => `${d.getFullYear()}-${kpPad(d.getMonth()+1)}-${kpPad(d.getDate())}`;
const kpSameDay = (a, b) => a.getFullYear()===b.getFullYear() && a.getMonth()===b.getMonth() && a.getDate()===b.getDate();
const kpAddDays = (d, n) => { const r = new Date(d); r.setDate(r.getDate()+n); return r; };
const kpStartOfMonthGrid = (year, month) => {
  // Monday-first 6-week grid
  const first = new Date(year, month, 1);
  const dow = (first.getDay() + 6) % 7; // Mon=0..Sun=6
  return kpAddDays(first, -dow);
};

// ---------- Placeholder occurrence generator ----------
// Maps course schedule strings → recurring weekday patterns. This is the
// stand-in for a real Google Calendar feed: same shape (date+course) so
// swapping it out is mechanical.
const KP_DOW_MAP = { 'Mo':1, 'Di':2, 'Mi':3, 'Do':4, 'Fr':5, 'Sa':6, 'So':0 };
const kpParseSchedule = (raw) => {
  // Returns { dows: [1..7], time: '10:00–11:30' } or null when ad-hoc
  if (!raw) return null;
  const dowMatches = [...raw.matchAll(/\b(Mo|Di|Mi|Do|Fr|Sa|So)\b/g)].map(m => KP_DOW_MAP[m[1]]);
  const timeMatch = raw.match(/(\d{1,2}:\d{2})\s*[–-]\s*(\d{1,2}:\d{2})/);
  if (!dowMatches.length) return null;
  return {
    dows: [...new Set(dowMatches)],
    time: timeMatch ? `${timeMatch[1]}–${timeMatch[2]}` : '',
  };
};

const kpBuildEvents = () => {
  // If a backend feed is present, use it as-is.
  if (Array.isArray(window.WB_KURSPLAN_EVENTS) && window.WB_KURSPLAN_EVENTS.length) {
    return window.WB_KURSPLAN_EVENTS;
  }
  // Otherwise generate ~6 months of recurring occurrences from course schedules.
  const events = [];
  const today = new Date();
  const start = new Date(today.getFullYear(), today.getMonth() - 1, 1);
  const end = new Date(today.getFullYear(), today.getMonth() + 5, 0);
  kpCourses.forEach(course => {
    const parsed = kpParseSchedule(course.schedule);
    if (!parsed) return;
    let cursor = new Date(start);
    while (cursor <= end) {
      if (parsed.dows.includes(cursor.getDay())) {
        events.push({
          id: `${course.id}-${kpKey(cursor)}`,
          courseId: course.id,
          date: new Date(cursor),
          time: parsed.time,
        });
      }
      cursor = kpAddDays(cursor, 1);
    }
  });
  return events;
};

// ---------- Filter bar (mirrors all-courses, compacted for inline use) ----------
const KpFilterBar = ({ activeCat, setActiveCat, leader, setLeader, query, setQuery, count }) => {
  // Collapse on small viewports. Mobile users open the panel only when they
  // want to filter — saves a ton of vertical space above the calendar.
  const [isMobile, setIsMobile] = useStateK(() =>
    typeof window !== 'undefined' && window.matchMedia
      ? window.matchMedia('(max-width: 720px)').matches
      : false
  );
  const [open, setOpen] = useStateK(false);

  useEffectK(() => {
    if (typeof window === 'undefined' || !window.matchMedia) return undefined;
    const mq = window.matchMedia('(max-width: 720px)');
    const onChange = (e) => {
      setIsMobile(e.matches);
      // When growing back to desktop, force the panel open so it always
      // shows; on shrink, collapse it again.
      if (!e.matches) setOpen(false);
    };
    onChange(mq);
    mq.addEventListener ? mq.addEventListener('change', onChange) : mq.addListener(onChange);
    return () => {
      mq.removeEventListener ? mq.removeEventListener('change', onChange) : mq.removeListener(onChange);
    };
  }, []);

  // Count active (non-default) filters for the summary chip
  const activeCount =
    (activeCat !== 'all' ? 1 : 0) +
    (leader !== 'all' ? 1 : 0) +
    (query.trim() ? 1 : 0);

  const activeCatLabel = activeCat === 'all'
    ? null
    : (kpCats.find(c => c.id === activeCat) || {}).title;

  const resetAll = (e) => {
    e.stopPropagation();
    setActiveCat('all');
    setLeader('all');
    setQuery('');
  };

  const expanded = !isMobile || open;

  return (
    <div style={{
      display: 'flex', flexDirection: 'column', gap: expanded ? 14 : 0,
      padding: isMobile ? '14px 16px' : '20px 22px',
      background: 'rgba(250, 246, 241, 0.6)',
      border: '1px solid rgba(196, 169, 143, 0.4)',
      borderRadius: 14,
      marginBottom: 24,
      backdropFilter: 'blur(10px)', WebkitBackdropFilter: 'blur(10px)',
      transition: 'gap 200ms var(--ease)',
    }}>
      {/* Mobile-only summary header — tap to expand/collapse */}
      {isMobile && (
        <button type="button"
          onClick={() => setOpen(o => !o)}
          aria-expanded={open}
          aria-controls="kp-filter-body"
          style={{
            display: 'flex', alignItems: 'center', justifyContent: 'space-between',
            gap: 10, padding: '4px 0',
            background: 'transparent', border: 'none',
            fontFamily: 'inherit', cursor: 'pointer',
            color: 'var(--ink)', textAlign: 'left',
          }}>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 10, minWidth: 0 }}>
            <span aria-hidden="true" style={{
              display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
              width: 22, height: 22,
              fontSize: 13, color: 'var(--ink-70)',
            }}>
              {/* Filter icon */}
              <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
                <path d="M2 4h12M4 8h8M6 12h4" />
              </svg>
            </span>
            <span style={{ fontSize: 13.5, fontWeight: 600, letterSpacing: '0.01em' }}>
              Filter
            </span>
            {activeCount > 0 ? (
              <span style={{
                display: 'inline-flex', alignItems: 'center',
                fontSize: 11.5, fontWeight: 600,
                padding: '2px 8px', borderRadius: 999,
                background: 'var(--heather-plum)', color: 'var(--offwhite)',
                lineHeight: 1.4,
              }}>
                {activeCount} aktiv
              </span>
            ) : (
              <span style={{
                fontSize: 12, color: 'var(--ink-50)',
                whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', minWidth: 0,
              }}>
                {activeCatLabel ? `Kategorie: ${activeCatLabel}` : `${count} ${count === 1 ? 'Termin' : 'Termine'}`}
              </span>
            )}
          </span>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
            {activeCount > 0 && (
              <span
                role="button"
                onClick={resetAll}
                style={{
                  fontSize: 11.5, fontWeight: 500,
                  color: 'var(--ink-70)', textDecoration: 'underline',
                  textUnderlineOffset: 2, cursor: 'pointer',
                }}
              >zurücksetzen</span>
            )}
            <span aria-hidden="true" style={{
              display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
              width: 22, height: 22,
              fontSize: 11, color: 'var(--ink-50)',
              transform: open ? 'rotate(180deg)' : 'rotate(0deg)',
              transition: 'transform 200ms var(--ease)',
            }}>▼</span>
          </span>
        </button>
      )}

      {/* Collapsible body */}
      <div
        id="kp-filter-body"
        style={{
          display: 'flex', flexDirection: 'column', gap: 14,
          overflow: 'hidden',
          maxHeight: expanded ? 800 : 0,
          opacity: expanded ? 1 : 0,
          marginTop: expanded && isMobile ? 6 : 0,
          transition: 'max-height 280ms var(--ease), opacity 180ms var(--ease), margin-top 200ms var(--ease)',
          pointerEvents: expanded ? 'auto' : 'none',
        }}
        aria-hidden={!expanded}
      >
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' }}>
          <KpCatChip active={activeCat === 'all'} onClick={() => setActiveCat('all')}
            label="Alle" tone={null} count={kpCourses.length} />
          {kpCats.map(c => {
            const n = kpCourses.filter(x => x.cat === c.id).length;
            return (
              <KpCatChip key={c.id} active={activeCat === c.id}
                onClick={() => setActiveCat(c.id)}
                label={c.title} tone={c} count={n} />
            );
          })}
        </div>

        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10, alignItems: 'center' }}>
          <div style={{ position: 'relative', flex: '1 1 240px', maxWidth: 320 }}>
            <input type="text" value={query} onChange={(e) => setQuery(e.target.value)}
              placeholder="Kurs suchen…"
              style={{
                width: '100%', padding: '10px 14px 10px 36px',
                background: 'var(--offwhite)',
                border: '1px solid var(--sandstone)', borderRadius: 10,
                fontSize: 13.5, fontFamily: 'inherit', color: 'var(--ink)',
                outline: 'none',
              }} />
            <span style={{
              position: 'absolute', left: 12, top: '50%', transform: 'translateY(-50%)',
              fontSize: 13, opacity: 0.5,
            }}>⌕</span>
          </div>

          <div style={{ position: 'relative', flex: '1 1 200px', minWidth: 0 }}>
            <select value={leader} onChange={(e) => setLeader(e.target.value)}
              style={{
                width: '100%',
                appearance: 'none', WebkitAppearance: 'none', MozAppearance: 'none',
                padding: '10px 36px 10px 14px',
                borderRadius: 10,
                background: 'var(--offwhite)',
                border: '1px solid var(--sandstone)',
                fontSize: 13.5, fontFamily: 'inherit', color: 'var(--ink)',
                fontWeight: 500, lineHeight: 1.4,
                cursor: 'pointer', outline: 'none',
              }}>
              <option value="all">Alle Kursleitungen</option>
              {kpLeaders.map(n => <option key={n} value={n}>{n}</option>)}
            </select>
            <span style={{
              position: 'absolute', right: 14, top: '50%',
              transform: 'translateY(-50%)',
              fontSize: 10, color: 'var(--ink-50)',
              pointerEvents: 'none', lineHeight: 1,
            }}>▼</span>
          </div>

          <span style={{ marginLeft: 'auto', fontSize: 12.5, color: 'var(--ink-70)' }}>
            {count} {count === 1 ? 'Termin' : 'Termine'} im Monat
          </span>
        </div>
      </div>
    </div>
  );
};

const KpCatChip = ({ active, onClick, label, tone, count }) => {
  const baseBg = tone ? tone.pill.bg : '#FAF6F1';
  const baseText = tone ? tone.pill.text : 'var(--ink-70)';
  const baseBorder = tone ? tone.pill.border : 'var(--sandstone)';
  return (
    <button type="button" onClick={onClick} style={{
      display: 'inline-flex', alignItems: 'center', gap: 7,
      padding: '7px 12px', borderRadius: 999,
      background: active && tone ? tone.tone.bg : (active ? 'var(--ink)' : baseBg),
      color: active ? (tone ? tone.tone.text : '#FAF6F1') : baseText,
      border: '1px solid ' + (active ? 'transparent' : baseBorder),
      fontSize: 12.5, fontWeight: 500, fontFamily: 'inherit',
      cursor: 'pointer', transition: 'all 160ms var(--ease)',
      whiteSpace: 'nowrap',
    }}>
      {tone && <span style={{
        width: 7, height: 7, borderRadius: 999,
        background: active ? 'rgba(255,255,255,0.9)' : tone.tone.solid,
      }} />}
      {label}
      <span style={{
        fontSize: 10.5, fontWeight: 600,
        opacity: active ? 0.75 : 0.55,
        marginLeft: 1,
      }}>{count}</span>
    </button>
  );
};

// ---------- Calendar grid ----------
const KpMonthGrid = ({ year, month, eventsByDay, onPickDay, selectedKey, onPrevMonth, onNextMonth }) => {
  // Touch-Swipe: links/rechts wischt durch die Monate.
  const _swipeRef = React.useRef({ x: 0, y: 0, active: false });
  const _onTouchStart = (e) => {
    const t = e.touches[0];
    _swipeRef.current = { x: t.clientX, y: t.clientY, active: true };
  };
  const _onTouchEnd = (e) => {
    if (!_swipeRef.current.active) return;
    const t = e.changedTouches[0];
    const dx = t.clientX - _swipeRef.current.x;
    const dy = t.clientY - _swipeRef.current.y;
    _swipeRef.current.active = false;
    if (Math.abs(dx) > 60 && Math.abs(dx) > Math.abs(dy) * 1.5) {
      if (dx < 0) { onNextMonth?.(); } else { onPrevMonth?.(); }
    }
  };

  const _onPick = (key, e) => {
    const rect = e.currentTarget.getBoundingClientRect();
    onPickDay(key, rect);
  };
  const today = new Date();
  const grid = useMemoK(() => {
    const start = kpStartOfMonthGrid(year, month);
    const out = [];
    for (let i = 0; i < 42; i++) out.push(kpAddDays(start, i));
    return out;
  }, [year, month]);

  return (
    <div
      onTouchStart={_onTouchStart}
      onTouchEnd={_onTouchEnd}
      onTouchCancel={_onTouchEnd}
      style={{
      background: 'var(--offwhite)',
      border: '1px solid rgba(196, 169, 143, 0.5)',
      borderRadius: 16,
      overflow: 'hidden',
      boxShadow: '0 4px 16px rgba(42,34,29,0.06)',
      touchAction: 'pan-y',
    }}>
      {/* DOW header */}
      <div className="kp-calendar-week" style={{
        display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)',
        background: 'rgba(232, 213, 192, 0.45)',
        borderBottom: '1px solid rgba(196, 169, 143, 0.4)',
      }}>
        {KP_DOW.map((d, i) => (
          <div key={d} style={{
            padding: '10px 12px',
            fontSize: 11, fontWeight: 600, letterSpacing: '0.12em',
            textTransform: 'uppercase',
            color: i >= 5 ? 'var(--ink-50)' : 'var(--ink-70)',
            textAlign: 'left',
          }}>{d}</div>
        ))}
      </div>

      {/* Day cells */}
      <div className="kp-calendar-grid" style={{
        display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)',
        gridAutoRows: 'minmax(108px, auto)',
      }}>
        {grid.map((d, i) => {
          const inMonth = d.getMonth() === month;
          const isToday = kpSameDay(d, today);
          const key = kpKey(d);
          const evs = eventsByDay[key] || [];
          const isSelected = selectedKey === key;
          const isWeekend = d.getDay() === 0 || d.getDay() === 6;
          const row = Math.floor(i / 7);
          const col = i % 7;
          return (
            <button key={key} type="button"
              className="kp-day-cell"
              data-kp-cell="1"
              onClick={(e) => evs.length && _onPick(key, e)}
              style={{
                textAlign: 'left',
                background: isSelected ? 'var(--heather-subtle)'
                  : (isToday ? 'rgba(232, 213, 192, 0.35)' : 'transparent'),
                border: 'none',
                borderRight: col < 6 ? '1px solid rgba(196, 169, 143, 0.2)' : 'none',
                borderTop: row > 0 ? '1px solid rgba(196, 169, 143, 0.2)' : 'none',
                padding: '8px 8px 10px',
                fontFamily: 'inherit',
                cursor: evs.length ? 'pointer' : 'default',
                opacity: inMonth ? 1 : 0.38,
                position: 'relative',
                minHeight: 108,
                minWidth: 0, overflow: 'hidden',
                display: 'flex', flexDirection: 'column', gap: 4,
                transition: 'background 140ms var(--ease)',
              }}
              onMouseEnter={(e) => {
                if (evs.length && !isSelected) {
                  e.currentTarget.style.background = 'rgba(239, 227, 231, 0.5)';
                }
              }}
              onMouseLeave={(e) => {
                if (!isSelected) {
                  e.currentTarget.style.background = isToday ? 'rgba(232, 213, 192, 0.35)' : 'transparent';
                }
              }}
            >
              <div style={{
                display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                marginBottom: 2,
              }}>
                <span style={{
                  fontSize: 13, fontWeight: isToday ? 700 : 500,
                  fontFamily: isToday ? 'Fraunces, serif' : 'inherit',
                  color: isWeekend && inMonth ? 'var(--ink-50)' : 'var(--ink)',
                  display: 'inline-flex', alignItems: 'center',
                  width: 22, height: 22, borderRadius: 999,
                  justifyContent: 'center',
                  background: isToday ? 'var(--heather-plum)' : 'transparent',
                  color: isToday ? 'var(--offwhite)' : (isWeekend && inMonth ? 'var(--ink-50)' : 'var(--ink)'),
                }}>{d.getDate()}</span>
              </div>
              {/* Mobile: kompakte Pills mit Kurz-Titel */}
              {evs.length > 0 && (
                <div className="kp-day-dots" style={{ display: 'none', flexDirection: 'column', gap: 2, minWidth: 0, overflow: 'hidden' }}>
                  {evs.slice(0, 2).map(ev => {
                    const c = kpCourseById[ev.courseId];
                    const cat = kpCatById[c.cat];
                    return (
                      <div key={ev.id} title={c.title} style={{
                        fontSize: 9, lineHeight: 1.2,
                        padding: '1px 4px',
                        borderRadius: 4,
                        background: cat.pill.bg,
                        color: cat.pill.text,
                        borderLeft: `2px solid ${cat.tone.solid}`,
                        whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
                        fontWeight: 500,
                      }}>
                        {c.shortTitle || c.title}
                      </div>
                    );
                  })}
                  {evs.length > 2 && (
                    <span style={{ fontSize: 9, color: 'var(--ink-50)', lineHeight: 1, padding: '0 4px' }}>
                      +{evs.length - 2}
                    </span>
                  )}
                </div>
              )}
              <div className="kp-day-pills" style={{ display: 'flex', flexDirection: 'column', gap: 3, overflow: 'hidden', minWidth: 0 }}>
                {evs.slice(0, 3).map(ev => {
                  const c = kpCourseById[ev.courseId];
                  const cat = kpCatById[c.cat];
                  return (
                    <div key={ev.id} style={{
                      fontSize: 10.5, lineHeight: 1.25,
                      padding: '3px 7px',
                      borderRadius: 6,
                      background: cat.pill.bg,
                      color: cat.pill.text,
                      borderLeft: `3px solid ${cat.tone.solid}`,
                      whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
                      fontWeight: 500,
                    }} title={`${c.title} · ${ev.time}`}>
                      {ev.time && <span style={{ fontVariantNumeric: 'tabular-nums', opacity: 0.7, marginRight: 4 }}>{ev.time.split('–')[0]}</span>}
                      {c.shortTitle || c.title}
                    </div>
                  );
                })}
                {evs.length > 3 && (
                  <div style={{
                    fontSize: 10, color: 'var(--ink-50)',
                    padding: '1px 7px', fontWeight: 500,
                  }}>+{evs.length - 3} weitere</div>
                )}
              </div>
            </button>
          );
        })}
      </div>
    </div>
  );
};

// ---------- Day detail popover (floats next to clicked cell) ----------
const KpDayPopover = ({ dayKey, anchorRect, events, onOpenInquiry, onClear }) => {
  const popRef = React.useRef(null);
  const [pos, setPos] = useStateK(null);

  // Position the popover next to the anchor cell — flip to the left or above
  // when there's not enough room on the right / below. Recomputes on resize
  // and scroll so it stays glued to its cell.
  useEffectK(() => {
    if (!dayKey || !anchorRect) return undefined;
    const W = 340, GAP = 10, MARGIN = 12;
    const compute = () => {
      const vw = window.innerWidth;
      const vh = window.innerHeight;
      const rect = anchorRect;
      // Horizontal: prefer right of cell; flip left if no room
      let left = rect.right + GAP;
      if (left + W > vw - MARGIN) left = rect.left - GAP - W;
      if (left < MARGIN) left = Math.max(MARGIN, Math.min(vw - W - MARGIN, rect.left));
      // Vertical: align top to cell top, then clamp into viewport
      const maxH = Math.min(420, vh - MARGIN * 2);
      let top = rect.top;
      if (top + maxH > vh - MARGIN) top = Math.max(MARGIN, vh - maxH - MARGIN);
      if (top < MARGIN) top = MARGIN;
      setPos({ left, top, maxH, width: W });
    };
    compute();
    window.addEventListener('resize', compute);
    window.addEventListener('scroll', compute, true);
    return () => {
      window.removeEventListener('resize', compute);
      window.removeEventListener('scroll', compute, true);
    };
  }, [dayKey, anchorRect]);

  // Outside-click + Escape dismiss
  useEffectK(() => {
    if (!dayKey) return undefined;
    const onDown = (e) => {
      if (popRef.current && !popRef.current.contains(e.target)) {
        // Don't close if click landed on a calendar day cell — let the cell
        // handler swap the selection cleanly.
        const cell = e.target.closest && e.target.closest('[data-kp-cell]');
        if (cell) return;
        onClear();
      }
    };
    const onKey = (e) => { if (e.key === 'Escape') onClear(); };
    document.addEventListener('mousedown', onDown);
    window.addEventListener('keydown', onKey);
    return () => {
      document.removeEventListener('mousedown', onDown);
      window.removeEventListener('keydown', onKey);
    };
  }, [dayKey, onClear]);

  if (!dayKey || !pos) return null;
  const [y, m, d] = dayKey.split('-').map(Number);
  const date = new Date(y, m-1, d);
  const dowLong = ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'][date.getDay()];

  return (
    <div ref={popRef} style={{
      position: 'fixed',
      left: pos.left, top: pos.top,
      width: pos.width, maxHeight: pos.maxH,
      background: 'var(--offwhite)',
      border: '1px solid rgba(196, 169, 143, 0.5)',
      borderRadius: 14,
      padding: '18px 20px 20px',
      boxShadow: '0 18px 48px rgba(42,34,29,0.22)',
      zIndex: 80,
      display: 'flex', flexDirection: 'column',
      animation: 'kpPop 180ms var(--ease)',
    }}>
      <style>{`@keyframes kpPop{from{opacity:0;transform:translateY(4px) scale(.985)}to{opacity:1;transform:translateY(0) scale(1)}}`}</style>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 14, flexShrink: 0 }}>
        <div>
          <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'var(--heather-plum)' }}>
            {dowLong}
          </div>
          <div style={{
            fontFamily: 'Fraunces, serif', fontSize: 24, lineHeight: 1.05,
            fontVariationSettings: '"opsz" 96, "SOFT" 50, "WONK" 1',
            color: 'var(--ink)', marginTop: 4,
          }}>{d}. {KP_MONTHS[m-1]}</div>
        </div>
        <button type="button" onClick={onClear} aria-label="Schließen" style={{
          width: 28, height: 28, borderRadius: 999,
          background: 'transparent', color: 'var(--ink-50)',
          border: '1px solid var(--sandstone-border)', fontSize: 15, lineHeight: 1,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          cursor: 'pointer',
        }}>×</button>
      </div>

      <div style={{
        display: 'flex', flexDirection: 'column', gap: 10,
        overflowY: 'auto', minHeight: 0,
        margin: '0 -4px', padding: '0 4px',
      }}>
        {events.length === 0 ? (
          <div style={{ color: 'var(--ink-70)', fontSize: 13.5 }}>Keine Termine an diesem Tag.</div>
        ) : events.map(ev => {
          const c = kpCourseById[ev.courseId];
          const cat = kpCatById[c.cat];
          // Klick auf die Karte (außer auf „Anfrage stellen") öffnet das
          // Kurs-Detail-Modal via CustomEvent — dieselbe Pipeline wie das
          // Kurs-Karussell unten, daher konsistent.
          const openCourseDetail = () => {
            onClear();
            // Kleiner Tick, damit der Day-Popup-Close fertig ist, bevor das
            // Modal aufploppt — sonst räumt der mousedown-handler oben das
            // gerade geöffnete Modal manchmal direkt wieder weg.
            setTimeout(() => {
              window.dispatchEvent(new CustomEvent('wb-open-course-by-id', {
                detail: { id: ev.courseId, title: c.title },
              }));
            }, 30);
          };
          return (
            <div key={ev.id}
              role="button" tabIndex={0}
              onClick={openCourseDetail}
              onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openCourseDetail(); } }}
              style={{
                border: '1px solid ' + cat.pill.border,
                borderLeft: `4px solid ${cat.tone.solid}`,
                borderRadius: 10,
                padding: '12px 14px',
                background: '#FAF6F1',
                display: 'flex', flexDirection: 'column', gap: 4,
                cursor: 'pointer',
                transition: 'transform 140ms var(--ease), box-shadow 140ms var(--ease)',
              }}
              onMouseEnter={(e) => {
                e.currentTarget.style.transform = 'translateY(-1px)';
                e.currentTarget.style.boxShadow = '0 4px 12px rgba(42,34,29,0.10)';
              }}
              onMouseLeave={(e) => {
                e.currentTarget.style.transform = 'translateY(0)';
                e.currentTarget.style.boxShadow = 'none';
              }}>
              <div style={{
                display: 'flex', alignItems: 'center', gap: 8,
                fontSize: 10.5, fontWeight: 600, letterSpacing: '0.12em', textTransform: 'uppercase',
                color: cat.pill.text,
              }}>
                <span style={{ width: 6, height: 6, borderRadius: 999, background: cat.tone.solid }} />
                {cat.title}
                {ev.time && (
                  <span style={{
                    marginLeft: 'auto',
                    fontSize: 11.5, color: 'var(--ink-70)',
                    fontVariantNumeric: 'tabular-nums', letterSpacing: 0,
                    textTransform: 'none', fontWeight: 500,
                  }}>{ev.time}</span>
                )}
              </div>
              <div style={{
                fontFamily: 'Fraunces, serif', fontSize: 17, lineHeight: 1.2,
                fontVariationSettings: '"opsz" 96, "SOFT" 50',
                color: 'var(--ink)',
              }}>{c.title}</div>
              <div style={{ fontSize: 12.5, color: 'var(--ink-70)' }}>
                Mit <span style={{ color: 'var(--ink)' }}>{c.leader}</span>
              </div>
              <button type="button"
                onClick={(e) => {
                  // Karte-onClick nicht mit triggern — sonst öffnen sich Modal
                  // UND Anfrageformular gleichzeitig.
                  e.stopPropagation();
                  onOpenInquiry({ id: ev.courseId, title: c.title, kind: 'course' });
                }}
                style={{
                  marginTop: 6, alignSelf: 'flex-start',
                  fontSize: 12, fontWeight: 600,
                  color: cat.pill.text,
                  background: 'transparent',
                  border: 'none', padding: 0, cursor: 'pointer',
                }}>Anfrage stellen →</button>
            </div>
          );
        })}
      </div>
    </div>
  );
};

// ---------- Wochen-/Tages-Agenda ----------
const _kpStartOfWeek = (d) => {
  const x = new Date(d); x.setHours(0, 0, 0, 0);
  x.setDate(x.getDate() - ((x.getDay() + 6) % 7));
  return x;
};
const _kpFilterDay = (events, isoDay) => events
  .filter((ev) => kpKey(ev.date) === isoDay)
  .sort((a, b) => (a.time || '').localeCompare(b.time || ''));

const _KpAgendaPill = ({ ev, onOpenCourse }) => {
  const c = kpCourseById[ev.courseId];
  if (!c) return null;
  const cat = kpCatById[c.cat] || { pill: { bg: 'var(--rose-tint)', text: 'var(--ink)' }, tone: { solid: 'var(--heather-plum)' } };
  // Wenn ein onOpenCourse-Handler reicht (Day-/Week-Ansicht), rendern wir
  // als <button>. Sonst (Fallback) als <div>, damit Eltern-Klick-Handler
  // funktionieren wie bisher.
  const clickable = typeof onOpenCourse === 'function';
  const Tag = clickable ? 'button' : 'div';
  const handleClick = clickable
    ? (e) => { e.stopPropagation(); onOpenCourse(ev); }
    : undefined;
  return (
    <Tag
      type={clickable ? 'button' : undefined}
      onClick={handleClick}
      style={{
        display: 'flex', alignItems: 'baseline', gap: 8,
        padding: '8px 12px',
        borderRadius: 8,
        background: cat.pill.bg,
        color: cat.pill.text,
        borderLeft: `3px solid ${cat.tone.solid}`,
        fontSize: 13, fontWeight: 500,
        fontFamily: 'inherit',
        textAlign: 'left',
        border: clickable ? 'none' : undefined,
        borderLeftWidth: clickable ? 3 : undefined,
        borderLeftStyle: clickable ? 'solid' : undefined,
        borderLeftColor: clickable ? cat.tone.solid : undefined,
        cursor: clickable ? 'pointer' : 'default',
        width: clickable ? '100%' : undefined,
        transition: clickable ? 'transform 140ms var(--ease), box-shadow 140ms var(--ease)' : undefined,
      }}
      onMouseEnter={clickable ? (e) => {
        e.currentTarget.style.transform = 'translateY(-1px)';
        e.currentTarget.style.boxShadow = '0 4px 12px rgba(42,34,29,0.10)';
      } : undefined}
      onMouseLeave={clickable ? (e) => {
        e.currentTarget.style.transform = 'translateY(0)';
        e.currentTarget.style.boxShadow = 'none';
      } : undefined}
      title={c.title}>
      {ev.time && (
        <span style={{ fontVariantNumeric: 'tabular-nums', opacity: 0.75, fontSize: 12, flexShrink: 0 }}>
          {ev.time}
        </span>
      )}
      <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
        {c.shortTitle || c.title}
      </span>
    </Tag>
  );
};

// Hilfsfunktion: Course-Detail-Modal aus dem Kalender heraus oeffnen
// (gleiches Pattern wie im Monats-Tagespopup — Event-Bus an course-carousel.jsx).
const _kpOpenCourseDetail = (ev) => {
  const c = kpCourseById[ev.courseId];
  if (!c) return;
  window.dispatchEvent(new CustomEvent('wb-open-course-by-id', {
    detail: { id: ev.courseId, title: c.title },
  }));
};

const KpWeekAgenda = ({ anchorDate, allEvents, onPickDay, onPrev, onNext }) => {
  const start = _kpStartOfWeek(new Date(anchorDate + 'T00:00:00'));
  const days = Array.from({ length: 7 }, (_, i) => {
    const d = new Date(start); d.setDate(start.getDate() + i); return d;
  });
  const todayIso = kpKey(new Date());

  // Swipe-Support
  const swipe = React.useRef({ x: 0, y: 0, active: false });
  const onTouchStart = (e) => { const t = e.touches[0]; swipe.current = { x: t.clientX, y: t.clientY, active: true }; };
  const onTouchEnd = (e) => {
    if (!swipe.current.active) return;
    const t = e.changedTouches[0];
    const dx = t.clientX - swipe.current.x, dy = t.clientY - swipe.current.y;
    swipe.current.active = false;
    if (Math.abs(dx) > 60 && Math.abs(dx) > Math.abs(dy) * 1.5) {
      if (dx < 0) onNext?.(); else onPrev?.();
    }
  };

  return (
    <div
      onTouchStart={onTouchStart} onTouchEnd={onTouchEnd} onTouchCancel={onTouchEnd}
      style={{
        background: 'var(--offwhite)',
        border: '1px solid rgba(196, 169, 143, 0.5)',
        borderRadius: 16, overflow: 'hidden',
        boxShadow: '0 4px 16px rgba(42,34,29,0.06)',
        touchAction: 'pan-y',
      }}>
      {days.map((d, i) => {
        const iso = kpKey(d);
        const events = _kpFilterDay(allEvents, iso);
        const isToday = iso === todayIso;
        const isWeekend = d.getDay() === 0 || d.getDay() === 6;
        return (
          <button
            key={iso}
            type="button"
            onClick={() => onPickDay?.(iso)}
            style={{
              width: '100%', display: 'flex', gap: 16,
              padding: '14px 18px',
              borderTop: i === 0 ? 'none' : '1px solid rgba(196, 169, 143, 0.25)',
              background: isToday ? 'var(--heather-subtle)' : 'transparent',
              border: 'none', textAlign: 'left',
              cursor: events.length ? 'pointer' : 'default',
              fontFamily: 'inherit',
              flexWrap: 'wrap',
            }}>
            <div style={{ width: 90, flexShrink: 0 }}>
              <div style={{
                fontSize: 11, fontWeight: 600, letterSpacing: '0.12em',
                textTransform: 'uppercase',
                color: isWeekend ? 'var(--ink-50)' : 'var(--ink-70)',
              }}>{d.toLocaleDateString('de-DE', { weekday: 'short' })}</div>
              <div style={{
                fontFamily: 'Fraunces, serif',
                fontSize: 20, fontWeight: 400, lineHeight: 1.1,
                color: isToday ? 'var(--heather-plum)' : 'var(--ink)',
                marginTop: 2,
              }}>{d.getDate()}. <span style={{ color: 'var(--ink-50)', fontSize: 13, fontWeight: 300 }}>{d.toLocaleDateString('de-DE', { month: 'short' })}</span></div>
            </div>
            <div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 4 }}>
              {events.length === 0
                ? <span style={{ color: 'var(--ink-50)', fontSize: 13, fontStyle: 'italic' }}>Keine Termine.</span>
                : events.map((ev) => (
                    <_KpAgendaPill key={ev.id} ev={ev} onOpenCourse={_kpOpenCourseDetail} />
                  ))}
            </div>
          </button>
        );
      })}
    </div>
  );
};

const KpDayAgenda = ({ anchorDate, allEvents, onPrev, onNext }) => {
  const d = new Date(anchorDate + 'T00:00:00');
  const events = _kpFilterDay(allEvents, anchorDate);
  const isToday = anchorDate === kpKey(new Date());

  const swipe = React.useRef({ x: 0, y: 0, active: false });
  const onTouchStart = (e) => { const t = e.touches[0]; swipe.current = { x: t.clientX, y: t.clientY, active: true }; };
  const onTouchEnd = (e) => {
    if (!swipe.current.active) return;
    const t = e.changedTouches[0];
    const dx = t.clientX - swipe.current.x, dy = t.clientY - swipe.current.y;
    swipe.current.active = false;
    if (Math.abs(dx) > 60 && Math.abs(dx) > Math.abs(dy) * 1.5) {
      if (dx < 0) onNext?.(); else onPrev?.();
    }
  };

  return (
    <div
      onTouchStart={onTouchStart} onTouchEnd={onTouchEnd} onTouchCancel={onTouchEnd}
      style={{
        background: 'var(--offwhite)',
        border: '1px solid rgba(196, 169, 143, 0.5)',
        borderRadius: 16, padding: 'clamp(18px, 3vw, 28px)',
        boxShadow: '0 4px 16px rgba(42,34,29,0.06)',
        touchAction: 'pan-y',
      }}>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 12, flexWrap: 'wrap', marginBottom: 14 }}>
        <span style={{
          fontFamily: 'Fraunces, serif',
          fontSize: 22, lineHeight: 1.1,
          color: isToday ? 'var(--heather-plum)' : 'var(--ink)',
        }}>{d.toLocaleDateString('de-DE', { weekday: 'long' })}</span>
        <span style={{ color: 'var(--ink-70)', fontSize: 15 }}>
          {d.toLocaleDateString('de-DE', { day: '2-digit', month: 'long', year: 'numeric' })}
        </span>
        {isToday && <span style={{
          fontSize: 11, fontWeight: 600, padding: '3px 10px', borderRadius: 999,
          background: 'var(--heather-subtle)', color: 'var(--heather-plum)',
          letterSpacing: '0.06em', textTransform: 'uppercase',
        }}>heute</span>}
      </div>
      {events.length === 0
        ? <p style={{ color: 'var(--ink-50)', fontSize: 14, fontStyle: 'italic', margin: 0 }}>Keine Termine an diesem Tag.</p>
        : <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            {events.map((ev) => (
              <_KpAgendaPill key={ev.id} ev={ev} onOpenCourse={_kpOpenCourseDetail} />
            ))}
          </div>}
    </div>
  );
};

// ---------- Top-level Kursplan section ----------
const Kursplan = ({ onOpenInquiry }) => {
  const headRef = useReveal();
  const today = new Date();
  const [year, setYear] = useStateK(today.getFullYear());
  const [month, setMonth] = useStateK(today.getMonth());
  // Anker für Wochen-/Tagesansicht
  const [anchorDate, setAnchorDate] = useStateK(() => kpKey(new Date()));
  // 'month' | 'week' | 'day'
  const [view, setView] = useStateK('month');

  const [activeCat, setActiveCat] = useStateK('all');
  const [leader, setLeader] = useStateK('all');
  const [query, setQuery] = useStateK('');
  const [selectedKey, setSelectedKey] = useStateK(null);
  const [anchorRect, setAnchorRect] = useStateK(null);

  const pickDay = (key, rect) => {
    setSelectedKey(key);
    setAnchorRect(rect);
  };
  const clearDay = () => {
    setSelectedKey(null);
    setAnchorRect(null);
  };

  // Datenversion bumpt nach Fetch — alle nachfolgenden useMemo re-evaluieren
  // mit den frischen window.WB_*-Arrays.
  const [dataVersion, setDataVersion] = useStateK(0);

  // Async-Fetch im Hintergrund. Cache wurde schon vor dem React-Mount
  // angewandt (siehe Top-Level-IIFE), daher rendert das UI bereits mit
  // dem zuletzt gespeicherten Stand. Wenn der fetch frische Daten liefert,
  // updaten wir + persistieren wir den Cache.
  useEffectK(() => {
    let stillMounted = true;
    fetch(WB_API_BASE_KP + '/api/public/kursplan')
      .then((r) => r.ok ? r.json() : Promise.reject(new Error('fetch failed')))
      .then((data) => {
        if (!stillMounted) return;
        if (!kpApplyData(data)) return;
        kpWriteCache(data);
        setDataVersion((v) => v + 1);
      })
      .catch(() => { /* Cache bleibt, sonst Hardcoded — beides ist OK */ });
    return () => { stillMounted = false; };
  }, []);

  // useMemo abhängig von dataVersion, damit nach Fetch frisch gebaut wird.
  // eslint-disable-next-line react-hooks/exhaustive-deps
  const allEvents = useMemoK(() => kpBuildEvents(), [dataVersion]);

  // Apply filters
  const filteredEvents = useMemoK(() => {
    const q = query.trim().toLowerCase();
    return allEvents.filter(ev => {
      const c = kpCourseById[ev.courseId];
      if (!c) return false;
      if (activeCat !== 'all' && c.cat !== activeCat) return false;
      if (leader !== 'all' && c.leader !== leader) return false;
      if (q && !(c.title.toLowerCase().includes(q) || c.desc.toLowerCase().includes(q) || c.leader.toLowerCase().includes(q))) return false;
      return true;
    });
  }, [allEvents, activeCat, leader, query]);

  // Group by day for the active month
  const eventsByDay = useMemoK(() => {
    const out = {};
    filteredEvents.forEach(ev => {
      if (ev.date.getFullYear() === year && ev.date.getMonth() === month) {
        const k = kpKey(ev.date);
        (out[k] = out[k] || []).push(ev);
      }
    });
    Object.values(out).forEach(list => list.sort((a, b) => (a.time || '').localeCompare(b.time || '')));
    return out;
  }, [filteredEvents, year, month]);

  const monthCount = useMemoK(() => Object.values(eventsByDay).reduce((s, v) => s + v.length, 0), [eventsByDay]);
  const selectedEvents = selectedKey ? (eventsByDay[selectedKey] || []) : [];

  const _shiftAnchor = (deltaDays) => {
    const d = new Date(anchorDate + 'T00:00:00');
    d.setDate(d.getDate() + deltaDays);
    setAnchorDate(kpKey(d));
    setYear(d.getFullYear());
    setMonth(d.getMonth());
  };
  const goPrev = () => {
    clearDay();
    if (view === 'month') {
      if (month === 0) { setYear(y => y - 1); setMonth(11); }
      else setMonth(m => m - 1);
      // Anker auf den 1. des neuen Monats
      const ny = month === 0 ? year - 1 : year;
      const nm = month === 0 ? 11 : month - 1;
      setAnchorDate(kpKey(new Date(ny, nm, 1)));
    } else {
      _shiftAnchor(view === 'week' ? -7 : -1);
    }
  };
  const goNext = () => {
    clearDay();
    if (view === 'month') {
      if (month === 11) { setYear(y => y + 1); setMonth(0); }
      else setMonth(m => m + 1);
      const ny = month === 11 ? year + 1 : year;
      const nm = month === 11 ? 0 : month + 1;
      setAnchorDate(kpKey(new Date(ny, nm, 1)));
    } else {
      _shiftAnchor(view === 'week' ? 7 : 1);
    }
  };
  const goToday = () => {
    const t = new Date();
    setYear(t.getFullYear()); setMonth(t.getMonth());
    setAnchorDate(kpKey(t));
    clearDay();
  };
  const switchView = (v) => {
    clearDay();
    setView(v);
    if (v !== 'month') {
      // Stelle sicher, dass anchorDate gesetzt ist
      if (!anchorDate) setAnchorDate(kpKey(new Date()));
    }
  };

  return (
    <section id="kursplan" style={{ padding: 'clamp(80px, 12vw, 128px) 0 clamp(40px, 6vw, 64px)' }}>
      <div className="container">
        <div ref={headRef} className="reveal" style={{
          marginBottom: 32, display: 'flex', alignItems: 'flex-end',
          justifyContent: 'space-between', gap: 24, flexWrap: 'wrap',
        }}>
          <div style={{ maxWidth: 720 }}>
            <div className="eyebrow" style={{ marginBottom: 16 }}>Kursplan</div>
            <h2 style={{
              fontSize: 'clamp(36px, 5vw, 56px)', lineHeight: 1.1, letterSpacing: '-0.015em',
              marginBottom: 20,
              fontVariationSettings: '"opsz" 96, "SOFT" 50, "WONK" 1',
            }}>
              Alle Termine auf{' '}
              <em className="bacalisties" style={{ color: 'var(--heather-plum)' }}>einen Blick</em>.
            </h2>
            <p className="lead" style={{ maxWidth: 600 }}>
              Wann läuft welcher Kurs? Filter nach Kategorie, Kursleitung oder Stichwort —
              und finde deinen passenden Termin.
            </p>
          </div>
        </div>

        <KpFilterBar
          activeCat={activeCat} setActiveCat={setActiveCat}
          leader={leader} setLeader={setLeader}
          query={query} setQuery={setQuery}
          count={monthCount}
        />

        {/* Period nav + View switcher */}
        <div style={{
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          marginBottom: 16, gap: 12, flexWrap: 'wrap',
        }}>
          <div style={{ display: 'inline-flex', alignItems: 'center', gap: 10 }}>
            <h3 style={{
              fontFamily: 'Fraunces, serif',
              fontSize: 'clamp(22px, 2.5vw, 28px)',
              fontVariationSettings: '"opsz" 96, "SOFT" 50, "WONK" 1',
              color: 'var(--ink)', lineHeight: 1.1,
            }}>{(() => {
              if (view === 'month') return (<>{KP_MONTHS[month]} <span style={{ color: 'var(--ink-50)', fontWeight: 300 }}>{year}</span></>);
              if (view === 'day') {
                const d = new Date(anchorDate + 'T00:00:00');
                return d.toLocaleDateString('de-DE', { weekday: 'long', day: '2-digit', month: 'long', year: 'numeric' });
              }
              // week
              const start = (() => { const x = new Date(anchorDate + 'T00:00:00'); x.setDate(x.getDate() - ((x.getDay() + 6) % 7)); return x; })();
              const end = new Date(start); end.setDate(start.getDate() + 6);
              return `${start.toLocaleDateString('de-DE', { day: '2-digit', month: 'short' })} – ${end.toLocaleDateString('de-DE', { day: '2-digit', month: 'short', year: 'numeric' })}`;
            })()}</h3>
          </div>
          <div style={{ display: 'inline-flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
            <div style={{
              display: 'inline-flex',
              background: 'var(--offwhite)',
              border: '1px solid var(--sandstone-border)',
              borderRadius: 999,
              padding: 2,
              fontSize: 12, fontWeight: 500, fontFamily: 'inherit',
            }}>
              {['month', 'week', 'day'].map((v) => (
                <button
                  key={v}
                  type="button"
                  onClick={() => switchView(v)}
                  style={{
                    padding: '6px 12px',
                    borderRadius: 999,
                    border: 'none',
                    background: view === v ? 'var(--heather-plum)' : 'transparent',
                    color: view === v ? 'var(--offwhite)' : 'var(--ink-70)',
                    cursor: 'pointer',
                    fontFamily: 'inherit',
                    transition: 'all 160ms var(--ease)',
                  }}>
                  {v === 'month' ? 'Monat' : v === 'week' ? 'Woche' : 'Tag'}
                </button>
              ))}
            </div>
            <button type="button" onClick={goToday} style={{
              fontSize: 12.5, fontWeight: 500,
              padding: '8px 14px', borderRadius: 999,
              background: 'transparent',
              border: '1px solid var(--sandstone-border)',
              color: 'var(--ink)',
              cursor: 'pointer', fontFamily: 'inherit',
              transition: 'all 160ms var(--ease)',
            }}
              onMouseEnter={(e)=>{ e.currentTarget.style.background='var(--heather-subtle)'; e.currentTarget.style.borderColor='var(--heather-plum)'; }}
              onMouseLeave={(e)=>{ e.currentTarget.style.background='transparent'; e.currentTarget.style.borderColor='var(--sandstone-border)'; }}
            >Heute</button>
            <KpNavBtn dir={-1} onClick={goPrev} />
            <KpNavBtn dir={1} onClick={goNext} />
          </div>
        </div>

        {/* Calendar — Monats-Grid oder Agenda je nach View */}
        {view === 'month' && (
          <KpMonthGrid
            year={year} month={month}
            eventsByDay={eventsByDay}
            onPickDay={pickDay}
            selectedKey={selectedKey}
            onPrevMonth={goPrev}
            onNextMonth={goNext}
          />
        )}
        {view === 'week' && (
          <KpWeekAgenda
            anchorDate={anchorDate}
            allEvents={filteredEvents}
            onPickDay={(iso) => { setAnchorDate(iso); setView('day'); clearDay(); }}
            onPrev={goPrev}
            onNext={goNext}
          />
        )}
        {view === 'day' && (
          <KpDayAgenda
            anchorDate={anchorDate}
            allEvents={filteredEvents}
            onPrev={goPrev}
            onNext={goNext}
          />
        )}

        {/* Floating day-detail popover — anchored to the clicked cell */}
        <KpDayPopover
          dayKey={selectedKey}
          anchorRect={anchorRect}
          events={selectedEvents}
          onOpenInquiry={onOpenInquiry}
          onClear={clearDay}
        />


      </div>
    </section>
  );
};

const KpNavBtn = ({ dir, onClick }) => (
  <button type="button" onClick={onClick} aria-label={dir < 0 ? 'Vorheriger Monat' : 'Nächster Monat'}
    style={{
      width: 36, height: 36, borderRadius: 999,
      background: 'var(--offwhite)',
      border: '1px solid var(--sandstone-border)',
      color: 'var(--ink)', fontSize: 14,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      cursor: 'pointer', fontFamily: 'inherit',
      transition: 'all 160ms var(--ease)',
    }}
    onMouseEnter={(e)=>{ e.currentTarget.style.background='var(--heather-plum)'; e.currentTarget.style.color='var(--offwhite)'; e.currentTarget.style.borderColor='var(--heather-plum)'; }}
    onMouseLeave={(e)=>{ e.currentTarget.style.background='var(--offwhite)'; e.currentTarget.style.color='var(--ink)'; e.currentTarget.style.borderColor='var(--sandstone-border)'; }}
  >{dir < 0 ? '←' : '→'}</button>
);

window.Kursplan = Kursplan;
