// Modal sheets: Wochenplan, Termine, Session-Detail

function Sheet({ open, onClose, title, children, fullHeight = false }) {
  const wide = window.useWide ? window.useWide() : false;
  const [render, setRender] = React.useState(open);
  const [visible, setVisible] = React.useState(open);
  React.useEffect(() => {
    if (open) {
      setRender(true);
      const t = setTimeout(() => setVisible(true), 20);
      return () => clearTimeout(t);
    } else {
      setVisible(false);
      const t = setTimeout(() => setRender(false), 280);
      return () => clearTimeout(t);
    }
  }, [open]);
  if (!render) return null;

  // Desktop / tablet → centered modal dialog
  if (wide) {
    return (
      <div style={{
        position: 'absolute', inset: 0, zIndex: 100, pointerEvents: 'auto',
        display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24,
      }}>
        <div onClick={onClose} style={{
          position: 'absolute', inset: 0,
          background: 'rgba(0,0,0,0.55)', backdropFilter: 'blur(2px)',
          opacity: visible ? 1 : 0, transition: 'opacity 240ms ease',
        }} />
        <div style={{
          position: 'relative',
          width: 'min(92vw, ' + (fullHeight ? 760 : 560) + 'px)',
          maxHeight: '88vh',
          background: 'var(--bg-deep)',
          borderRadius: 24, border: '1px solid var(--border)',
          boxShadow: '0 40px 90px rgba(0,0,0,0.55)',
          transform: visible ? 'scale(1)' : 'scale(0.96)',
          opacity: visible ? 1 : 0,
          transition: 'transform 240ms cubic-bezier(.2,.8,.2,1), opacity 200ms ease',
          display: 'flex', flexDirection: 'column', overflow: 'hidden',
        }}>
          <div style={{
            display: 'flex', alignItems: 'center', justifyContent: 'space-between',
            padding: '18px 20px 14px', borderBottom: '1px solid var(--border)',
          }}>
            <div style={{
              fontFamily: 'Space Grotesk, system-ui', fontSize: 20, fontWeight: 700,
              color: 'var(--text)', letterSpacing: -0.3,
            }}>{title}</div>
            <button onClick={onClose} style={{
              appearance: 'none', cursor: 'pointer',
              width: 32, height: 32, borderRadius: 999,
              background: 'var(--surface)', border: '1px solid var(--border)',
              color: 'var(--muted)', fontSize: 17,
              display: 'flex', alignItems: 'center', justifyContent: 'center',
            }}>×</button>
          </div>
          <div style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden', paddingTop: 14 }}>
            {children}
          </div>
        </div>
      </div>
    );
  }

  // Mobile → bottom sheet
  return (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 100,
      pointerEvents: 'auto',
    }}>
      <div onClick={onClose} style={{
        position: 'absolute', inset: 0,
        background: 'rgba(0,0,0,0.55)',
        backdropFilter: 'blur(2px)',
        opacity: visible ? 1 : 0,
        transition: 'opacity 240ms ease',
      }} />
      <div style={{
        position: 'absolute', left: 0, right: 0, bottom: 0,
        height: fullHeight ? '92%' : 'auto',
        maxHeight: '92%',
        background: 'var(--bg-deep)',
        borderTopLeftRadius: 28, borderTopRightRadius: 28,
        border: '1px solid var(--border)', borderBottom: 0,
        transform: visible ? 'translateY(0)' : 'translateY(100%)',
        transition: 'transform 320ms cubic-bezier(.2,.8,.2,1)',
        display: 'flex', flexDirection: 'column',
        overflow: 'hidden',
      }}>
        <div style={{
          padding: '10px 0 6px', display: 'flex', justifyContent: 'center',
        }}>
          <div style={{
            width: 36, height: 4, borderRadius: 999,
            background: 'var(--border)',
          }} />
        </div>
        <div style={{
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          padding: '8px 16px 12px',
        }}>
          <div style={{
            fontFamily: 'Space Grotesk, system-ui', fontSize: 19, fontWeight: 700,
            color: 'var(--text)', letterSpacing: -0.3,
          }}>{title}</div>
          <button onClick={onClose} style={{
            appearance: 'none', cursor: 'pointer',
            width: 30, height: 30, borderRadius: 999,
            background: 'var(--surface)', border: '1px solid var(--border)',
            color: 'var(--muted)', fontSize: 16,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}>×</button>
        </div>
        <div style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden' }}>
          {children}
        </div>
      </div>
    </div>
  );
}

// Large, finger-friendly − / value / + control for picking an hour.
// Full-width split layout (NN/g): big −/+ targets pinned to the edges so they
// can never be clipped on narrow screens, with the label + value centered.
function HourStepper({ label, value, onDec, onInc, decDisabled, incDisabled }) {
  const StepBtn = ({ onClick, disabled, children, side }) => (
    <button type="button" disabled={disabled} onClick={disabled ? undefined : onClick} style={{
      appearance: 'none', cursor: disabled ? 'default' : 'pointer',
      width: 56, height: 56, flexShrink: 0, padding: 0,
      background: 'transparent',
      borderTop: 0, borderBottom: 0,
      [side === 'left' ? 'borderRight' : 'borderLeft']: '1px solid var(--border)',
      [side === 'left' ? 'borderLeft' : 'borderRight']: 0,
      color: disabled ? 'color-mix(in oklab, var(--muted) 35%, transparent)' : 'var(--accent)',
      fontFamily: 'Space Grotesk, system-ui', fontSize: 26, fontWeight: 600, lineHeight: 1,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
    }}>{children}</button>
  );
  return (
    <div style={{
      display: 'flex', alignItems: 'center',
      background: 'var(--bg-deep)', border: '1px solid var(--border)',
      borderRadius: 12, height: 56, overflow: 'hidden',
    }}>
      <StepBtn onClick={onDec} disabled={decDisabled} side="left">−</StepBtn>
      <div style={{
        flex: 1, minWidth: 0, textAlign: 'center',
        display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 1,
      }}>
        <span style={{
          fontFamily: 'Space Grotesk, system-ui', fontSize: 10, fontWeight: 600,
          letterSpacing: 0.8, textTransform: 'uppercase', color: 'var(--muted)',
        }}>{label}</span>
        <span style={{
          fontFamily: 'JetBrains Mono, monospace', fontSize: 18, fontWeight: 600,
          color: 'var(--text)',
        }}>{window.fmtHour(value)}</span>
      </div>
      <StepBtn onClick={onInc} disabled={incDisabled} side="right">+</StepBtn>
    </div>
  );
}

// Wochenplan
function SheetWochenplan({ open, onClose }) {
  const t = window.t;
  const [slots, setSlots] = React.useState(() => JSON.parse(JSON.stringify(window.WEEKLY.me)));
  // moods: { [dow]: Set of moodIds } — multi-select, loaded from Firestore
  const [moods, setMoods] = React.useState(() => {
    const saved = window.__MY_MOODS;
    if (saved && Object.keys(saved).length) {
      // clone Sets so edits don't mutate the cached copy
      return Object.fromEntries(Object.entries(saved).map(([k, v]) => [k, new Set(v)]));
    }
    return {};
  });
  const toggleMood = (dow, mood) => {
    setMoods(prev => {
      const cur = new Set(prev[dow] || []);
      if (cur.has(mood)) cur.delete(mood); else cur.add(mood);
      return { ...prev, [dow]: cur };
    });
  };

  // Per-day group selection (multi-select) — only relevant with >1 group.
  // Shape: { [dow]: Set<groupId> }. Defaults to all of my groups for each day.
  const myGroups = window.GROUPS.filter(g => g.memberIds.includes('me'));
  const [dayGroups, setDayGroups] = React.useState(() => {
    const saved = window.__MY_WEEKLY_GROUPS || {};
    return Object.fromEntries(
      Object.entries(saved).map(([k, v]) => [Number(k), new Set(v)])
    );
  });
  const groupsForDay = (dow) =>
    dayGroups[dow] || new Set(myGroups.map(g => g.id));
  const toggleDayGroup = (dow, gid) => {
    setDayGroups(prev => {
      const next = new Set(prev[dow] || myGroups.map(g => g.id));
      if (next.has(gid)) next.delete(gid); else next.add(gid);
      return { ...prev, [dow]: next };
    });
  };

  // Selectable hour span: 14:00 (2pm) … 27:00 (3am next day) = 13 one-hour steps.
  const HOUR_MIN = 14;
  const HOUR_MAX = 27;

  const setFrom = (dow, val) => setSlots(prev => {
    const cur = prev[dow];
    if (!cur || cur.length !== 2) return prev;
    const nf = Math.max(HOUR_MIN, Math.min(val, cur[1] - 1));
    return { ...prev, [dow]: [nf, cur[1]] };
  });
  const setTo = (dow, val) => setSlots(prev => {
    const cur = prev[dow];
    if (!cur || cur.length !== 2) return prev;
    const nt = Math.min(HOUR_MAX, Math.max(val, cur[0] + 1));
    return { ...prev, [dow]: [cur[0], nt] };
  });
  const addSlot   = (dow) => setSlots(prev => ({ ...prev, [dow]: [20, 23] }));
  const clearSlot = (dow) => setSlots(prev => ({ ...prev, [dow]: [] }));

  return (
    <Sheet open={open} onClose={onClose} title={t('wochenplan')} fullHeight>
      <div style={{ padding: '0 16px 24px' }}>
        <div style={{
          fontFamily: 'Space Grotesk, system-ui', fontSize: 13,
          color: 'var(--muted)', marginBottom: 16, lineHeight: 1.5,
        }}>
          {t('wochenplan_intro_a')} <em style={{ color: 'var(--text)' }}>{t('wochenplan_intro_b')}</em> {t('wochenplan_intro_c')}
        </div>

        <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          {window.dayLabelsLong().map((day, dow) => {
            const slot = slots[dow] || [];
            const hasSlot = slot.length === 2;
            const moodSet = moods[dow] || new Set();
            const moodList = [...moodSet];
            return (
              <div key={dow} style={{
                background: 'var(--surface)',
                border: `1px solid ${hasSlot ? 'color-mix(in oklab, var(--accent) 20%, var(--border))' : 'var(--border)'}`,
                borderRadius: 16, padding: 14,
              }}>
                <div style={{
                  display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                  marginBottom: 10, gap: 8, flexWrap: 'wrap',
                }}>
                  <div style={{ display: 'flex', alignItems: 'baseline', gap: 8 }}>
                    <div style={{
                      fontFamily: 'Space Grotesk, system-ui', fontSize: 15, fontWeight: 600,
                      color: 'var(--text)',
                    }}>{day}</div>
                    {hasSlot && (
                      <Mono size={12} color="var(--accent)">
                        {window.rangeStr(slot[0], slot[1])}
                      </Mono>
                    )}
                  </div>
                  {hasSlot && moodList.length > 0 && (
                    <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
                      {moodList.map(m => <MoodBadge key={m} mood={m} />)}
                    </div>
                  )}
                </div>

                {hasSlot ? (
                  <div>
                    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                      <HourStepper
                        label={t('von_label')} value={slot[0]}
                        onDec={() => setFrom(dow, slot[0] - 1)} onInc={() => setFrom(dow, slot[0] + 1)}
                        decDisabled={slot[0] <= HOUR_MIN} incDisabled={slot[0] >= slot[1] - 1}
                      />
                      <HourStepper
                        label={t('bis_label')} value={slot[1]}
                        onDec={() => setTo(dow, slot[1] - 1)} onInc={() => setTo(dow, slot[1] + 1)}
                        decDisabled={slot[1] <= slot[0] + 1} incDisabled={slot[1] >= HOUR_MAX}
                      />
                    </div>
                    {/* Visual span bar */}
                    <div style={{
                      marginTop: 12, height: 8, borderRadius: 999,
                      background: 'var(--bg-deep)', border: '1px solid var(--border)',
                      position: 'relative', overflow: 'hidden',
                    }}>
                      <div style={{
                        position: 'absolute', top: 0, bottom: 0,
                        left: `${(slot[0] - HOUR_MIN) / (HOUR_MAX - HOUR_MIN) * 100}%`,
                        right: `${(HOUR_MAX - slot[1]) / (HOUR_MAX - HOUR_MIN) * 100}%`,
                        background: 'var(--accent)',
                      }} />
                    </div>
                    <button type="button" onClick={() => clearSlot(dow)} style={{
                      appearance: 'none', cursor: 'pointer',
                      marginTop: 12, padding: '8px 14px', borderRadius: 999,
                      background: 'transparent',
                      border: '1px solid color-mix(in oklab, var(--pink) 30%, var(--border))',
                      color: 'var(--pink)',
                      fontFamily: 'Space Grotesk, system-ui', fontSize: 12, fontWeight: 700,
                    }}>{t('zeit_entfernen')}</button>
                  </div>
                ) : (
                  <button type="button" onClick={() => addSlot(dow)} style={{
                    appearance: 'none', cursor: 'pointer', width: '100%',
                    height: 48, borderRadius: 12,
                    background: 'var(--bg-deep)',
                    border: '1px dashed color-mix(in oklab, var(--accent) 40%, var(--border))',
                    color: 'var(--accent)',
                    fontFamily: 'Space Grotesk, system-ui', fontSize: 14, fontWeight: 600,
                  }}>{t('zeit_hinzufuegen')}</button>
                )}

                {hasSlot && (
                  <div style={{ marginTop: 12 }}>
                    <div style={{
                      fontFamily: 'Space Grotesk, system-ui', fontSize: 10, fontWeight: 600,
                      letterSpacing: 0.8, textTransform: 'uppercase',
                      color: 'var(--muted)', marginBottom: 6,
                    }}>{(window.__LANG || 'de') === 'en' ? 'Mood (multi)' : 'Lust auf (mehrfach)'}</div>
                    <div style={{
                      display: 'flex', gap: 6, flexWrap: 'wrap',
                    }}>
                      {Object.keys(window.MOODS).map(m => {
                        const on = moodSet.has(m);
                        return (
                          <button key={m} onClick={() => toggleMood(dow, m)} style={{
                            appearance: 'none', cursor: 'pointer',
                            background: 'transparent', border: 0, padding: 0,
                          }}>
                            <span style={{
                              display: 'inline-flex', alignItems: 'center', gap: 5,
                              fontFamily: 'Space Grotesk, system-ui', fontSize: 11, fontWeight: 600,
                              letterSpacing: 0.4, textTransform: 'uppercase',
                              color: on
                                ? `oklch(0.85 0.16 ${window.MOODS[m].hue})`
                                : 'var(--muted)',
                              padding: '4px 9px', borderRadius: 999,
                              background: on
                                ? `oklch(0.85 0.16 ${window.MOODS[m].hue} / 0.14)`
                                : 'var(--bg-deep)',
                              border: on
                                ? `1px solid oklch(0.85 0.16 ${window.MOODS[m].hue} / 0.4)`
                                : '1px solid var(--border)',
                            }}>
                              <span style={{ fontSize: 9 }}>{window.MOODS[m].glyph}</span>
                              {window.moodLabel(m)}
                              {on && <span style={{ fontSize: 9, opacity: 0.7, marginLeft: 2 }}>✓</span>}
                            </span>
                          </button>
                        );
                      })}
                    </div>
                  </div>
                )}

                {hasSlot && myGroups.length > 1 && (
                  <div style={{ marginTop: 12 }}>
                    <div style={{
                      fontFamily: 'Space Grotesk, system-ui', fontSize: 10, fontWeight: 600,
                      letterSpacing: 0.8, textTransform: 'uppercase',
                      color: 'var(--muted)', marginBottom: 6,
                    }}>{t('fuer_gruppen')}</div>
                    <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
                      {myGroups.map(g => {
                        const on = groupsForDay(dow).has(g.id);
                        return (
                          <button key={g.id} type="button" onClick={() => toggleDayGroup(dow, g.id)} style={{
                            appearance: 'none', cursor: 'pointer',
                            display: 'inline-flex', alignItems: 'center', gap: 5,
                            padding: '4px 9px', borderRadius: 999,
                            background: on ? `oklch(0.85 0.16 ${g.hue} / 0.14)` : 'var(--bg-deep)',
                            border: `1px solid ${on ? `oklch(0.85 0.16 ${g.hue} / 0.4)` : 'var(--border)'}`,
                            color: on ? `oklch(0.85 0.16 ${g.hue})` : 'var(--muted)',
                            fontFamily: 'Space Grotesk, system-ui', fontSize: 11, fontWeight: 600,
                            letterSpacing: 0.3, textTransform: 'uppercase',
                          }}>
                            <span style={{ fontSize: 10 }}>{g.glyph}</span>
                            {window.groupName(g)}
                            {on && <span style={{ fontSize: 9, opacity: 0.8 }}>✓</span>}
                          </button>
                        );
                      })}
                    </div>
                  </div>
                )}
              </div>
            );
          })}
        </div>

        <div style={{ marginTop: 18 }}>
          <Button full onClick={() => {
            window.fbSaveWeekly?.(slots);
            window.fbSaveMoods?.(moods);
            if (myGroups.length > 1) {
              const out = {};
              for (const [dow, set] of Object.entries(dayGroups)) out[dow] = [...set];
              window.fbSaveWeeklyGroups?.(out);
            }
            onClose();
          }}>{t('speichern')}</Button>
        </div>
      </div>
    </Sheet>
  );
}

// Termine
const TERMINE_GREEN = 'oklch(0.82 0.17 145)';
// Base month the calendar opens on — always the real current month so the
// date picker starts on today.
const _TERMINE_NOW = new Date();
const TERMINE_BASE_YEAR = _TERMINE_NOW.getFullYear();
const TERMINE_BASE_MONTH = _TERMINE_NOW.getMonth(); // 0-indexed
const TERMINE_MONTHS_DE = ['Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'];
const TERMINE_MONTHS_EN = ['January','February','March','April','May','June','July','August','September','October','November','December'];

function SheetTermine({ open, onClose }) {
  const t = window.t;
  const [picked, setPicked] = React.useState(() => {
    // Migrate any legacy day-number keys ("28") to full date keys ("2026-4-28")
    const raw = window.__MY_FREE_DATES || {};
    const out = {};
    for (const [k, v] of Object.entries(raw)) {
      out[/^\d+$/.test(String(k)) ? `${TERMINE_BASE_YEAR}-${TERMINE_BASE_MONTH}-${k}` : k] = v;
    }
    return out;
  });
  const [detailDay, setDetailDay] = React.useState(null);
  const [monthOffset, setMonthOffset] = React.useState(0);
  const lang = window.__LANG || 'de';

  // Resolve the month currently being viewed from the base month + offset
  const viewDate = new Date(TERMINE_BASE_YEAR, TERMINE_BASE_MONTH + monthOffset, 1);
  const year = viewDate.getFullYear();
  const month = viewDate.getMonth();
  const monthName = `${(lang === 'en' ? TERMINE_MONTHS_EN : TERMINE_MONTHS_DE)[month]} ${year}`;
  // Monday-first column index of the 1st of the month (JS getDay: 0=Sun)
  const firstDow = (new Date(year, month, 1).getDay() + 6) % 7;
  const days = new Date(year, month + 1, 0).getDate();
  const isCurrentMonth = year === TERMINE_BASE_YEAR && month === TERMINE_BASE_MONTH;
  const today = isCurrentMonth ? window.TODAY_DATE : -1;
  const keyOf = (d) => `${year}-${month}-${d}`;
  const myGroups = window.GROUPS.filter(g => g.memberIds.includes('me'));

  // Reset to calendar view whenever the sheet (re)opens
  React.useEffect(() => { if (open) { setDetailDay(null); setMonthOffset(0); } }, [open]);

  const grid = [];
  for (let i = 0; i < firstDow; i++) grid.push(null);
  for (let d = 1; d <= days; d++) grid.push(d);
  while (grid.length % 7 !== 0) grid.push(null);

  const dowOf = (d) => (firstDow + (d - 1)) % 7;
  const hasRecurring = (d) => (window.WEEKLY.me?.[dowOf(d)]?.length === 2);

  const openDay = (d) => {
    const k = keyOf(d);
    setPicked(prev => prev[k] && typeof prev[k] === 'object'
      ? prev
      : { ...prev, [k]: { time: null, groups: myGroups.map(g => g.id), moods: [] } });
    setDetailDay(k);
  };
  const removeDay = (k) => {
    setPicked(prev => { const n = { ...prev }; delete n[k]; return n; });
    setDetailDay(null);
  };
  const entry = detailDay != null ? (picked[detailDay] || {}) : null;

  const DETAIL_HOUR_MIN = 14;
  const DETAIL_HOUR_MAX = 27;
  const setDetailFrom = (val) => setPicked(prev => {
    const e = prev[detailDay] || {}; const cur = e.time;
    if (!cur || cur.length !== 2) return prev;
    const nf = Math.max(DETAIL_HOUR_MIN, Math.min(val, cur[1] - 1));
    return { ...prev, [detailDay]: { ...e, time: [nf, cur[1]] } };
  });
  const setDetailTo = (val) => setPicked(prev => {
    const e = prev[detailDay] || {}; const cur = e.time;
    if (!cur || cur.length !== 2) return prev;
    const nt = Math.min(DETAIL_HOUR_MAX, Math.max(val, cur[0] + 1));
    return { ...prev, [detailDay]: { ...e, time: [cur[0], nt] } };
  });
  const addDetailSlot   = () => setPicked(prev => ({ ...prev, [detailDay]: { ...(prev[detailDay] || {}), time: [20, 23] } }));
  const clearDetailTime = () => setPicked(prev => ({ ...prev, [detailDay]: { ...(prev[detailDay] || {}), time: null } }));
  const toggleDetailMood = (m) => setPicked(prev => {
    const e = prev[detailDay] || {};
    const cur = new Set(e.moods || []);
    if (cur.has(m)) cur.delete(m); else cur.add(m);
    return { ...prev, [detailDay]: { ...e, moods: [...cur] } };
  });
  const toggleDetailGroup = (gid) => setPicked(prev => {
    const e = prev[detailDay] || {};
    const cur = new Set(e.groups || myGroups.map(g => g.id));
    if (cur.has(gid)) cur.delete(gid); else cur.add(gid);
    return { ...prev, [detailDay]: { ...e, groups: [...cur] } };
  });

  const dateLabel = (k) => {
    const [, m, d] = String(k).split('-').map(Number);
    return lang === 'en'
      ? `${TERMINE_MONTHS_EN[m]} ${d}` : `${d}. ${TERMINE_MONTHS_DE[m]}`;
  };

  // ── Detail view for a single date ──────────────────────────────────────────
  if (detailDay != null) {
    const e = entry;
    const time = e.time || null;
    const moodSet = new Set(e.moods || []);
    const groupSet = new Set(e.groups || myGroups.map(g => g.id));
    return (
      <Sheet open={open} onClose={onClose} title={`${t('freie_termine')} · ${dateLabel(detailDay)}`} fullHeight>
        <div style={{ padding: '0 16px 24px' }}>
          <button type="button" onClick={() => setDetailDay(null)} style={{
            appearance: 'none', cursor: 'pointer', background: 'transparent', border: 0,
            color: 'var(--muted)', fontFamily: 'Space Grotesk, system-ui', fontSize: 13,
            fontWeight: 600, padding: '0 0 16px', display: 'flex', alignItems: 'center', gap: 6,
          }}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M15 6l-6 6 6 6" strokeLinecap="round" strokeLinejoin="round"/></svg>
            {lang === 'en' ? 'Calendar' : 'Kalender'}
          </button>

          {/* Time */}
          <div style={{ marginBottom: 20 }}>
            <div style={{
              fontFamily: 'Space Grotesk, system-ui', fontSize: 11, fontWeight: 600,
              letterSpacing: 0.8, textTransform: 'uppercase', color: 'var(--muted)',
              marginBottom: 8, display: 'flex', alignItems: 'center', gap: 8,
            }}>
              {t('termine_zeit')}
              <span style={{ color: time ? 'var(--accent)' : 'var(--muted)', textTransform: 'none', letterSpacing: 0 }}>
                {time ? window.rangeStr(time[0], time[1]) : t('termine_ganzer_tag')}
              </span>
            </div>
            {time ? (
              <div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                  <HourStepper
                    label={t('von_label')} value={time[0]}
                    onDec={() => setDetailFrom(time[0] - 1)} onInc={() => setDetailFrom(time[0] + 1)}
                    decDisabled={time[0] <= DETAIL_HOUR_MIN} incDisabled={time[0] >= time[1] - 1}
                  />
                  <HourStepper
                    label={t('bis_label')} value={time[1]}
                    onDec={() => setDetailTo(time[1] - 1)} onInc={() => setDetailTo(time[1] + 1)}
                    decDisabled={time[1] <= time[0] + 1} incDisabled={time[1] >= DETAIL_HOUR_MAX}
                  />
                </div>
                <div style={{
                  marginTop: 12, height: 8, borderRadius: 999,
                  background: 'var(--bg-deep)', border: '1px solid var(--border)',
                  position: 'relative', overflow: 'hidden',
                }}>
                  <div style={{
                    position: 'absolute', top: 0, bottom: 0,
                    left: `${(time[0] - DETAIL_HOUR_MIN) / (DETAIL_HOUR_MAX - DETAIL_HOUR_MIN) * 100}%`,
                    right: `${(DETAIL_HOUR_MAX - time[1]) / (DETAIL_HOUR_MAX - DETAIL_HOUR_MIN) * 100}%`,
                    background: 'var(--accent)',
                  }} />
                </div>
                <button type="button" onClick={clearDetailTime} style={{
                  appearance: 'none', cursor: 'pointer',
                  marginTop: 12, padding: '8px 14px', borderRadius: 999,
                  background: 'transparent', border: '1px solid var(--border)',
                  color: 'var(--muted)',
                  fontFamily: 'Space Grotesk, system-ui', fontSize: 12, fontWeight: 700,
                }}>{t('termine_ganzer_tag')}</button>
              </div>
            ) : (
              <button type="button" onClick={addDetailSlot} style={{
                appearance: 'none', cursor: 'pointer', width: '100%',
                height: 48, borderRadius: 12,
                background: 'var(--bg-deep)',
                border: '1px dashed color-mix(in oklab, var(--accent) 40%, var(--border))',
                color: 'var(--accent)',
                fontFamily: 'Space Grotesk, system-ui', fontSize: 14, fontWeight: 600,
              }}>{t('zeit_hinzufuegen')}</button>
            )}
          </div>

          {/* Groups */}
          {myGroups.length > 1 && (
            <div style={{ marginBottom: 20 }}>
              <div style={{
                fontFamily: 'Space Grotesk, system-ui', fontSize: 11, fontWeight: 600,
                letterSpacing: 0.8, textTransform: 'uppercase', color: 'var(--muted)', marginBottom: 8,
              }}>{t('fuer_gruppen')}</div>
              <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
                {myGroups.map(g => {
                  const on = groupSet.has(g.id);
                  return (
                    <button key={g.id} type="button" onClick={() => toggleDetailGroup(g.id)} style={{
                      appearance: 'none', cursor: 'pointer',
                      display: 'inline-flex', alignItems: 'center', gap: 5,
                      padding: '6px 11px', borderRadius: 999,
                      background: on ? `oklch(0.85 0.16 ${g.hue} / 0.14)` : 'var(--bg-deep)',
                      border: `1px solid ${on ? `oklch(0.85 0.16 ${g.hue} / 0.4)` : 'var(--border)'}`,
                      color: on ? `oklch(0.85 0.16 ${g.hue})` : 'var(--muted)',
                      fontFamily: 'Space Grotesk, system-ui', fontSize: 12, fontWeight: 600,
                    }}>
                      <span style={{ fontSize: 11 }}>{g.glyph}</span>
                      {window.groupName(g)}
                      {on && <span style={{ fontSize: 10, opacity: 0.8 }}>✓</span>}
                    </button>
                  );
                })}
              </div>
            </div>
          )}

          {/* Mood */}
          <div style={{ marginBottom: 24 }}>
            <div style={{
              fontFamily: 'Space Grotesk, system-ui', fontSize: 11, fontWeight: 600,
              letterSpacing: 0.8, textTransform: 'uppercase', color: 'var(--muted)', marginBottom: 8,
            }}>{t('mood_multi')}</div>
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
              {Object.keys(window.MOODS).map(m => {
                const on = moodSet.has(m);
                return (
                  <button key={m} type="button" onClick={() => toggleDetailMood(m)} style={{
                    appearance: 'none', cursor: 'pointer',
                    display: 'inline-flex', alignItems: 'center', gap: 5,
                    fontFamily: 'Space Grotesk, system-ui', fontSize: 11, fontWeight: 600,
                    letterSpacing: 0.4, textTransform: 'uppercase',
                    color: on ? `oklch(0.85 0.16 ${window.MOODS[m].hue})` : 'var(--muted)',
                    padding: '6px 11px', borderRadius: 999,
                    background: on ? `oklch(0.85 0.16 ${window.MOODS[m].hue} / 0.14)` : 'var(--bg-deep)',
                    border: on ? `1px solid oklch(0.85 0.16 ${window.MOODS[m].hue} / 0.4)` : '1px solid var(--border)',
                  }}>
                    <span style={{ fontSize: 9 }}>{window.MOODS[m].glyph}</span>
                    {window.moodLabel(m)}
                    {on && <span style={{ fontSize: 9, opacity: 0.7 }}>✓</span>}
                  </button>
                );
              })}
            </div>
          </div>

          <Button full onClick={() => setDetailDay(null)}>{t('termine_uebernehmen')}</Button>
          <div style={{ marginTop: 10 }}>
            <Button variant="danger" full onClick={() => removeDay(detailDay)}>{t('tag_entfernen')}</Button>
          </div>
        </div>
      </Sheet>
    );
  }

  // ── Calendar view ───────────────────────────────────────────────────────────
  return (
    <Sheet open={open} onClose={onClose} title={t('freie_termine')} fullHeight>
      <div style={{ padding: '0 16px 24px' }}>
        <div style={{
          fontFamily: 'Space Grotesk, system-ui', fontSize: 13,
          color: 'var(--muted)', marginBottom: 16, lineHeight: 1.5,
        }}>
          {t('termine_intro_a')} <em style={{ color: 'var(--text)' }}>{t('termine_intro_b')}</em> {t('termine_intro_c')}
        </div>

        <div style={{
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          marginBottom: 12,
        }}>
          <RoundIconBtn onClick={() => setMonthOffset(o => Math.max(0, o - 1))} disabled={monthOffset === 0}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M15 6l-6 6 6 6" strokeLinecap="round" strokeLinejoin="round"/></svg>
          </RoundIconBtn>
          <div style={{
            fontFamily: 'Space Grotesk, system-ui', fontSize: 16, fontWeight: 600,
            color: 'var(--text)',
          }}>{monthName}</div>
          <RoundIconBtn onClick={() => setMonthOffset(o => o + 1)}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><path d="M9 6l6 6-6 6" strokeLinecap="round" strokeLinejoin="round"/></svg>
          </RoundIconBtn>
        </div>

        <div style={{
          display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 4,
          marginBottom: 6,
        }}>
          {window.dayLabelsShort().map(d => (
            <div key={d} style={{
              textAlign: 'center',
              fontFamily: 'Space Grotesk, system-ui', fontSize: 10, fontWeight: 600,
              letterSpacing: 0.8, textTransform: 'uppercase',
              color: 'var(--muted)',
            }}>{d}</div>
          ))}
        </div>

        <div style={{
          display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 4,
        }}>
          {grid.map((d, i) => {
            if (d === null) return <div key={i} />;
            const isToday = d === today;
            const isPicked = !!picked[keyOf(d)];
            const recurring = hasRecurring(d);
            const border = isPicked
              ? 'color-mix(in oklab, var(--accent) 40%, var(--border))'
              : isToday ? 'var(--accent)'
              : recurring ? TERMINE_GREEN
              : 'var(--border)';
            return (
              <button key={i} onClick={() => openDay(d)} style={{
                appearance: 'none', cursor: 'pointer',
                aspectRatio: '1', borderRadius: 10,
                background: isPicked
                  ? 'color-mix(in oklab, var(--accent) 22%, var(--surface))'
                  : 'var(--surface)',
                border: `1px solid ${border}`,
                boxShadow: recurring && !isPicked && !isToday ? `0 0 0 1px ${TERMINE_GREEN}` : 'none',
                color: isPicked ? 'var(--accent)' : 'var(--text)',
                display: 'flex', flexDirection: 'column',
                alignItems: 'center', justifyContent: 'center', gap: 2,
                position: 'relative',
                fontFamily: 'JetBrains Mono, monospace', fontSize: 13, fontWeight: 600,
                opacity: d < today ? 0.3 : 1,
              }}>
                <span>{d}</span>
              </button>
            );
          })}
        </div>

        <div style={{
          display: 'flex', alignItems: 'center', gap: 14, marginTop: 16, flexWrap: 'wrap',
          fontFamily: 'Space Grotesk, system-ui', fontSize: 11, color: 'var(--muted)',
        }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <div style={{
              width: 10, height: 10, borderRadius: 3,
              background: 'color-mix(in oklab, var(--accent) 22%, var(--surface))',
              border: '1px solid color-mix(in oklab, var(--accent) 40%, var(--border))',
            }} />
            {t('du_frei')}
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <div style={{
              width: 10, height: 10, borderRadius: 3,
              background: 'var(--surface)',
              border: `1px solid ${TERMINE_GREEN}`,
              boxShadow: `0 0 0 1px ${TERMINE_GREEN}`,
            }} />
            {t('termine_recurring')}
          </div>
        </div>

        <div style={{ marginTop: 18 }}>
          <Button full onClick={() => { window.fbSaveFreeDates?.(picked); onClose(); }}>{t('speichern')}</Button>
        </div>
      </div>
    </Sheet>
  );
}

// Session detail
// Readiness check ("Seid ihr bereit?") block shown inside a session. Lets any
// participant ask the crew if they're ready, lets each member answer
// Bereit / Komme gleich, and shows everyone's status as avatars ringed in the
// matching color (green = ready, amber = on the way, grey = no reply yet).
function SessionReadyCheck({ session }) {
  const t = window.t;
  const [, bump] = React.useReducer(x => x + 1, 0);
  if (!session) return null;

  const active = window.sessionReadyCheckActive(session);
  const targets = window.sessionReadyTargets(session);
  const myStatus = window.sessionReadyStatus(session, 'me');
  const meAccepted = window.sessionMemberStatus(session, 'me') !== 'out';
  const { ready, total } = window.sessionReadyCount(session);

  const ask = async () => {
    await window.fbRequestReadyCheck?.(session);
    const n = window.sessionReadyTargets(session).filter(id => id !== 'me').length;
    window.zockerToast?.({ title: t('ready_sent_title'), body: t('ready_sent_body', n) });
    bump();
  };
  const answer = async (s) => { await window.fbSetReady?.(session, s); bump(); };

  return (
    <div style={{ marginBottom: 22 }}>
      <SectionHead title={active ? `${t('ready_check')} · ${t('ready_count', ready, total)}` : t('ready_check')} />

      {!active ? (
        <button onClick={ask} style={{
          appearance: 'none', cursor: 'pointer', width: '100%',
          padding: '12px 14px', borderRadius: 14,
          background: 'color-mix(in oklab, var(--accent) 14%, var(--surface))',
          border: '1px solid color-mix(in oklab, var(--accent) 40%, var(--border))',
          color: 'var(--accent)',
          fontFamily: 'Space Grotesk, system-ui', fontSize: 14, fontWeight: 700,
          display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
        }}>
          <span style={{ fontSize: 16 }}>⚡</span>{t('ready_check_btn')}
        </button>
      ) : (
        <>
          {/* Roster: accepted members ringed by their readiness */}
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 14, marginBottom: 14 }}>
            {targets.map(fid => {
              const f = window.FRIENDS_BY_ID[fid];
              if (!f) return null;
              const st = window.sessionReadyStatus(session, fid);
              const meta = window.readyStatusMeta(st);
              return (
                <div key={fid} style={{
                  display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6, width: 56,
                }}>
                  <Avatar id={fid} size={40} ringColor={meta.color} />
                  <span style={{
                    fontFamily: 'Space Grotesk, system-ui', fontSize: 10, fontWeight: 700,
                    letterSpacing: 0.2, color: meta.color, textAlign: 'center', lineHeight: 1.2,
                  }}>{meta.label}</span>
                </div>
              );
            })}
          </div>

          {/* My own answer */}
          {meAccepted && (
            <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6, marginBottom: 8 }}>
              {[['yes', t('ready_yes'), 'var(--accent)'], ['coming', t('ready_coming'), 'oklch(0.82 0.16 75)']].map(([id, label, color]) => {
                const on = myStatus === id;
                return (
                  <button key={id} onClick={() => answer(id)} style={{
                    appearance: 'none', cursor: 'pointer',
                    padding: '12px 8px', borderRadius: 12,
                    background: on ? `color-mix(in oklab, ${color} 20%, var(--surface))` : 'var(--surface)',
                    border: `1px solid ${on ? color : 'var(--border)'}`,
                    color: on ? color : 'var(--text)',
                    fontFamily: 'Space Grotesk, system-ui', fontSize: 13, fontWeight: 700,
                  }}>{label}</button>
                );
              })}
            </div>
          )}

          <button onClick={ask} style={{
            appearance: 'none', cursor: 'pointer', width: '100%',
            padding: '8px 12px', borderRadius: 10,
            background: 'transparent', border: '1px solid var(--border)',
            color: 'var(--muted)',
            fontFamily: 'Space Grotesk, system-ui', fontSize: 12, fontWeight: 600,
          }}>↻ {t('ready_check_again')}</button>
        </>
      )}
    </div>
  );
}

function SheetSession({ session, onClose }) {
  const t = window.t;
  const key = window.sessionKey?.(session);
  const saved = (window.__MY_RESPONSES || {})[key];
  const [rsvp, setRsvp] = React.useState(saved?.rsvp || 'in');
  const [voteGameId, setVoteGameId] = React.useState(saved?.voteGameId || session?.topGame?.id);
  const [, forceRerender] = React.useReducer(x => x + 1, 0);
  // Vote-list animation bookkeeping: DOM nodes per game, their last positions
  // (for the FLIP reorder slide), and which game was just voted (for the pulse).
  const voteRowRefs = React.useRef({});
  const voteCountRefs = React.useRef({});
  const votePrevRects = React.useRef({});
  const votePulseGid = React.useRef(null);
  // Re-sync when a different session is opened (component stays mounted)
  React.useEffect(() => {
    const s = (window.__MY_RESPONSES || {})[key];
    setRsvp(s?.rsvp || 'in');
    setVoteGameId(s?.voteGameId || session?.topGame?.id);
    votePrevRects.current = {}; // don't slide rows when switching sessions
  }, [key]);
  // After each render: slide rows that changed position (FLIP) and pulse the
  // card + count of the game just voted for, so the vote is visible.
  React.useLayoutEffect(() => {
    const rows = voteRowRefs.current;
    const newRects = {};
    for (const gid in rows) { if (rows[gid]) newRects[gid] = rows[gid].getBoundingClientRect(); }
    for (const gid in newRects) {
      const prev = votePrevRects.current[gid];
      const el = rows[gid];
      if (prev && el) {
        const dy = prev.top - newRects[gid].top;
        if (Math.abs(dy) > 1) {
          el.style.transition = 'none';
          el.style.transform = `translateY(${dy}px)`;
          requestAnimationFrame(() => {
            el.style.transition = 'transform 360ms cubic-bezier(.2,.8,.2,1)';
            el.style.transform = '';
          });
        }
      }
    }
    votePrevRects.current = newRects;

    const pg = votePulseGid.current;
    if (pg) {
      const el = rows[pg];
      if (el) { el.style.animation = 'none'; void el.offsetWidth; el.style.animation = 'vote-glow 700ms ease'; }
      const c = voteCountRefs.current[pg];
      if (c) { c.style.animation = 'none'; void c.offsetWidth; c.style.animation = 'vote-count-pop 600ms ease'; }
      votePulseGid.current = null;
    }
  });
  // Persist the answer the moment it changes — no Save button needed. The RSVP
  // and the manual single vote are stored automatically. fbSaveResponse updates
  // window.__MY_RESPONSES synchronously, so a re-render reflects the new tallies
  // (e.g. declining yourself immediately drops you from the available count).
  const persist = (nextRsvp, nextVoteGameId) => {
    window.fbSaveResponse?.(key, { rsvp: nextRsvp, voteGameId: nextVoteGameId || null });
    forceRerender();
  };
  const chooseRsvp = (id) => { setRsvp(id); persist(id, voteGameId); };
  const castVote = (gid) => { votePulseGid.current = gid; setVoteGameId(gid); persist(rsvp, gid); };
  if (!session) return null;
  // Available members = everyone free for this session minus those who declined.
  const availableIds = window.sessionAvailableIds(session);
  const availableCount = availableIds.length;
  // Vote tally is over available members only — declined members never count.
  const sg = window.sessionGameVotes(session);
  const dayLabel = session.dayOffset === 0 ? t('heute')
    : session.dayOffset === 1 ? t('morgen')
    : window.dayLabelsLong()[session.dow];

  return (
    <Sheet open={!!session} onClose={onClose} title={t('session')} fullHeight>
      <div style={{ padding: '0 16px 24px' }}>
        <div style={{
          padding: 18, borderRadius: 18,
          background: 'linear-gradient(155deg, color-mix(in oklab, var(--accent) 18%, var(--surface)) 0%, var(--surface) 70%)',
          border: '1px solid color-mix(in oklab, var(--accent) 25%, var(--border))',
          marginBottom: 18,
        }}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 6 }}>
            <div style={{
              fontFamily: 'Space Grotesk, system-ui', fontSize: 11, fontWeight: 700,
              color: 'var(--accent)', letterSpacing: 1.5, textTransform: 'uppercase',
            }}>{dayLabel}</div>
            {session.groups && session.groups.length > 0 && (
              <window.GroupBadges groups={session.groups} max={3} />
            )}
          </div>
          <div style={{
            display: 'flex', alignItems: 'baseline', gap: 10,
            marginBottom: 14,
          }}>
            <Mono size={36} color="var(--text)" style={{ fontWeight: 600, letterSpacing: -1 }}>
              {window.fmtHour(session.from)}
            </Mono>
            <span style={{ color: 'var(--muted)', fontSize: 18 }}>–</span>
            <Mono size={20} color="var(--muted)">{window.fmtHour(session.to)}</Mono>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <AvatarStack ids={availableIds} size={30} />
            <span style={{
              fontFamily: 'Space Grotesk, system-ui', fontSize: 13, color: 'var(--text)',
            }}>
              {t('n_frei', availableCount)}
            </span>
          </div>
        </div>

        <SectionHead title={t('deine_antwort')} />
        <div style={{
          display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 6,
          marginBottom: 22,
        }}>
          {[
            ['in',    t('dabei_rsvp'),  'var(--accent)'],
            ['maybe', t('maybe'),       'var(--violet)'],
            ['out',   t('kann_nicht'),  'var(--pink)'],
          ].map(([id, label, color]) => {
            const active = rsvp === id;
            return (
              <button key={id} onClick={() => chooseRsvp(id)} style={{
                appearance: 'none', cursor: 'pointer',
                padding: '12px 8px', borderRadius: 12,
                background: active ? `color-mix(in oklab, ${color} 18%, var(--surface))` : 'var(--surface)',
                border: `1px solid ${active ? color : 'var(--border)'}`,
                color: active ? color : 'var(--text)',
                fontFamily: 'Space Grotesk, system-ui', fontSize: 13, fontWeight: 600,
              }}>{label}</button>
            );
          })}
        </div>

        {/* Crew roster — everyone's status is visible to the whole crew.
            Declining drops you out for everyone; maybe shows up too. */}
        <SectionHead title={t('teilnehmer')} />
        <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginBottom: 22 }}>
          {session.friends.map(fid => {
            const f = window.FRIENDS_BY_ID[fid];
            if (!f) return null;
            const status = window.sessionMemberStatus(session, fid);
            const meta = {
              in:    { label: t('dabei_rsvp'), color: 'var(--accent)' },
              maybe: { label: t('maybe'),      color: 'var(--violet)' },
              out:   { label: t('kann_nicht'), color: 'var(--pink)' },
            }[status];
            const out = status === 'out';
            return (
              <div key={fid} style={{
                display: 'flex', alignItems: 'center', gap: 10,
                padding: '8px 10px', borderRadius: 12,
                background: 'var(--surface)', border: '1px solid var(--border)',
                opacity: out ? 0.55 : 1,
              }}>
                <Avatar id={fid} size={28} />
                <span style={{
                  flex: 1, minWidth: 0,
                  fontFamily: 'Space Grotesk, system-ui', fontSize: 13, fontWeight: 600,
                  color: 'var(--text)',
                  overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                  textDecoration: out ? 'line-through' : 'none',
                }}>{fid === 'me' ? t('du') : f.name}</span>
                <span style={{
                  display: 'inline-flex', alignItems: 'center', gap: 5,
                  fontFamily: 'Space Grotesk, system-ui', fontSize: 11, fontWeight: 700,
                  letterSpacing: 0.4, textTransform: 'uppercase',
                  color: meta.color, flexShrink: 0,
                }}>
                  <span style={{ width: 7, height: 7, borderRadius: '50%', background: meta.color }} />
                  {meta.label}
                </span>
              </div>
            );
          })}
        </div>

        <window.SessionReadyCheck session={session} />

        <SectionHead title={`${t('game_vote')} · ${sg[0]?.votes || 0}/${sg[0]?.total || availableCount} ${t('top')}`} />
        <div style={{
          fontFamily: 'Space Grotesk, system-ui', fontSize: 12, color: 'var(--muted)',
          margin: '-4px 0 10px',
        }}>{t('vote_hint')}</div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
          {sg.slice(0, 6).map(({ game, votes, total }) => {
            const isTop = votes === sg[0].votes;
            const picked = voteGameId === game.id;
            return (
              <button key={game.id}
                ref={el => { if (el) voteRowRefs.current[game.id] = el; else delete voteRowRefs.current[game.id]; }}
                onClick={() => castVote(game.id)} style={{
                appearance: 'none', cursor: 'pointer', textAlign: 'left',
                background: picked ? 'color-mix(in oklab, var(--accent) 10%, var(--surface))' : 'var(--surface)',
                border: `1px solid ${picked ? 'color-mix(in oklab, var(--accent) 35%, var(--border))' : 'var(--border)'}`,
                borderRadius: 14, padding: 10,
                display: 'flex', alignItems: 'center', gap: 12,
                position: 'relative', overflow: 'hidden',
                willChange: 'transform',
              }}>
                <div style={{
                  position: 'absolute', inset: 0,
                  background: `linear-gradient(90deg, color-mix(in oklab, var(--accent) ${isTop ? 8 : 4}%, transparent) ${Math.min(100, (votes/(total||1))*100)}%, transparent ${Math.min(100, (votes/(total||1))*100)}%)`,
                  pointerEvents: 'none',
                }} />
                <GameCover game={game} size={44} rounded={10} />
                <div style={{ flex: 1, minWidth: 0, position: 'relative' }}>
                  <div style={{
                    fontFamily: 'Space Grotesk, system-ui', fontSize: 14, fontWeight: 600,
                    color: 'var(--text)',
                    overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
                  }}>{game.title}</div>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 2 }}>
                    <span style={{
                      fontFamily: 'JetBrains Mono, monospace', fontSize: 10, color: 'var(--muted)',
                    }}>{window.gameGenreLabel(game) || game.tag}</span>
                    <ModeBadge modes={game.modes} />
                  </div>
                </div>
                <div style={{
                  display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 2,
                  position: 'relative',
                }}>
                  <span
                    ref={el => { if (el) voteCountRefs.current[game.id] = el; else delete voteCountRefs.current[game.id]; }}
                    style={{ display: 'inline-block', transformOrigin: 'right center' }}>
                    <Mono size={14} color={isTop ? 'var(--accent)' : 'var(--text)'} style={{ fontWeight: 700 }}>
                      {votes}/{total}
                    </Mono>
                  </span>
                  {isTop && (
                    <div style={{
                      fontFamily: 'Space Grotesk, system-ui', fontSize: 9, fontWeight: 700,
                      color: 'var(--accent)', letterSpacing: 0.8, textTransform: 'uppercase',
                    }}>★ {t('top')}</div>
                  )}
                </div>
              </button>
            );
          })}
        </div>

        {/* Who is backing the currently selected game. A member who both picked
            AND voted for it shows up twice (once per contribution). */}
        {voteGameId && (() => {
          const backers = window.sessionGameBackers(session, voteGameId);
          const game = window.GAMES_BY_ID[voteGameId];
          return (
            <div style={{
              marginTop: 14, padding: 14, borderRadius: 14,
              background: 'var(--surface)', border: '1px solid var(--border)',
            }}>
              <div style={{
                fontFamily: 'Space Grotesk, system-ui', fontSize: 11, fontWeight: 600,
                letterSpacing: 0.8, textTransform: 'uppercase',
                color: 'var(--muted)', marginBottom: 10,
              }}>{t('gewaehlt_von')}{game ? ` · ${game.title}` : ''}</div>
              {backers.length === 0 ? (
                <div style={{
                  fontFamily: 'Space Grotesk, system-ui', fontSize: 13, color: 'var(--muted)',
                }}>{t('niemand_gewaehlt')}</div>
              ) : (
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10 }}>
                  {backers.map(({ fid, kind }, i) => (
                    <div key={fid + '_' + kind + '_' + i} style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
                      <Avatar id={fid} size={28} />
                      <span style={{
                        fontFamily: 'Space Grotesk, system-ui', fontSize: 13, fontWeight: 500,
                        color: 'var(--text)',
                      }}>{fid === 'me' ? t('du') : (window.FRIENDS_BY_ID[fid]?.name || '?')}</span>
                      <span style={{
                        fontFamily: 'Space Grotesk, system-ui', fontSize: 9, fontWeight: 700,
                        letterSpacing: 0.5, textTransform: 'uppercase',
                        color: kind === 'vote' ? 'var(--accent)' : 'var(--violet)',
                        background: kind === 'vote'
                          ? 'color-mix(in oklab, var(--accent) 15%, transparent)'
                          : 'color-mix(in oklab, var(--violet) 15%, transparent)',
                        padding: '2px 6px', borderRadius: 999,
                      }}>{kind === 'vote' ? t('vote_label') : t('pick_label')}</span>
                    </div>
                  ))}
                </div>
              )}
            </div>
          );
        })()}

        <div style={{
          marginTop: 14, padding: 14, borderRadius: 14,
          background: 'var(--surface)', border: '1px solid var(--border)',
          display: 'flex', alignItems: 'center', gap: 12,
        }}>
          <div style={{
            width: 36, height: 36, borderRadius: 10,
            background: 'color-mix(in oklab, var(--violet) 18%, transparent)',
            color: 'var(--violet)',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}>
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <path d="M12 1v22M5 8v8M19 8v8M2 11v2M22 11v2M8.5 4v16M15.5 4v16" />
            </svg>
          </div>
          <div style={{ flex: 1 }}>
            <div style={{
              fontFamily: 'Space Grotesk, system-ui', fontSize: 14, fontWeight: 600,
              color: 'var(--text)',
            }}>{t('voice_channel')}</div>
            <Mono size={11} color="var(--muted)" style={{ marginTop: 2, display: 'block' }}>
              crew-zocken-1
            </Mono>
          </div>
          <Chip color="violet">{t('beitreten')}</Chip>
        </div>

        {/* Answer + vote are saved automatically — no Save button needed. */}
        <div style={{
          marginTop: 18, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 7,
          fontFamily: 'Space Grotesk, system-ui', fontSize: 12, color: 'var(--muted)',
        }}>
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
            <path d="M5 12l5 5L20 7" />
          </svg>
          {t('auto_saved')}
        </div>
      </div>
    </Sheet>
  );
}

// Add Friend sheet — invite by email to a group
function SheetAddFriend({ open, onClose, initialGroupId }) {
  const t       = window.t;
  const lang    = window.__LANG || 'de';
  const de      = lang !== 'en';
  const [mode, setMode]       = React.useState('email'); // 'email' | 'link' | 'share'
  const [email, setEmail]     = React.useState('');
  const [groupId, setGroupId] = React.useState(() => initialGroupId || window.GROUPS?.[0]?.id || '');
  const [status, setStatus]   = React.useState(null); // null | 'loading' | 'added' | 'invited' | 'error'
  const [errMsg, setErrMsg]   = React.useState('');
  const [sentList, setSentList] = React.useState([]);
  const [link, setLink]       = React.useState(null); // { url, groupId }
  const [linkBusy, setLinkBusy] = React.useState(false);
  const [copied, setCopied]   = React.useState(false);
  const [shareNote, setShareNote] = React.useState('');

  // Refresh group list whenever sheet opens. If opened from a specific group
  // (e.g. a Crew page), pre-select that group.
  React.useEffect(() => {
    if (open) {
      setEmail('');
      setStatus(null);
      setMode('email');
      setLink(null);
      setCopied(false);
      setShareNote('');
      const valid = initialGroupId && window.GROUPS_BY_ID?.[initialGroupId];
      if (valid) setGroupId(initialGroupId);
      else if (!groupId && window.GROUPS?.[0]) setGroupId(window.GROUPS[0].id);
      // Load pending invites sent by me (email invites only)
      const uid = window.__FB_UID;
      if (uid && window.fbDb) {
        window.fbDb.collection('invites')
          .where('fromUid', '==', uid)
          .orderBy('createdAt', 'desc')
          .limit(20)
          .get()
          .then(snap => setSentList(
            snap.docs.map(d => ({ id: d.id, ...d.data() })).filter(i => i.toEmail).slice(0, 10)
          ))
          .catch(() => {});
      }
    }
  }, [open]);

  // Clear the "copied" hint when switching group/mode.
  React.useEffect(() => { setCopied(false); setShareNote(''); }, [groupId, mode]);

  const handleInvite = async (e) => {
    e.preventDefault();
    if (!email.trim()) return;
    setStatus('loading');
    try {
      const result = await window.fbInviteFriend(email.trim(), groupId);
      setStatus(result); // 'added' or 'invited'
      setEmail('');
    } catch (err) {
      if (err.message === 'already_member') {
        setErrMsg(de ? 'Diese Person ist bereits in der Gruppe.' : 'That person is already in the group.');
      } else {
        setErrMsg(err.message || (de ? 'Fehler beim Einladen.' : 'Error sending invite.'));
      }
      setStatus('error');
    }
  };

  // Create (or reuse) a shareable link for the currently-selected group.
  const ensureLink = async () => {
    if (link && link.groupId === groupId) return link;
    setLinkBusy(true);
    try {
      const res = await window.fbCreateInviteLink(groupId);
      const l = { url: res.url, groupId };
      setLink(l);
      return l;
    } catch (e) {
      setShareNote(de ? 'Fehler beim Erstellen des Links.' : 'Could not create the link.');
      return null;
    } finally {
      setLinkBusy(false);
    }
  };

  const copyToClipboard = async (text) => {
    try {
      if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(text); return true; }
    } catch (_) { /* fall through */ }
    try {
      const ta = document.createElement('textarea');
      ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0';
      document.body.appendChild(ta); ta.focus(); ta.select();
      const ok = document.execCommand('copy');
      document.body.removeChild(ta);
      return ok;
    } catch (_) { return false; }
  };

  const handleCopy = async () => {
    const l = await ensureLink();
    if (!l) return;
    if (await copyToClipboard(l.url)) { setCopied(true); setTimeout(() => setCopied(false), 2000); }
  };

  const handleShare = async () => {
    const l = await ensureLink();
    if (!l) return;
    const group = window.GROUPS_BY_ID?.[groupId];
    const gName = group ? window.groupName(group) : '';
    if (navigator.share) {
      try { await navigator.share({ title: 'Zocker', text: t('invite_share_text', gName), url: l.url }); return; }
      catch (e) { if (e && e.name === 'AbortError') return; }
    }
    if (await copyToClipboard(l.url)) { setCopied(true); setTimeout(() => setCopied(false), 2000); }
    setShareNote(t('invite_share_fallback'));
  };

  const groups = window.GROUPS || [];

  const inputStyle = {
    width: '100%', padding: '12px 14px', boxSizing: 'border-box',
    background: 'var(--bg-deep)', border: '1px solid var(--border)',
    borderRadius: 12, color: 'var(--text)',
    fontFamily: 'Space Grotesk, system-ui', fontSize: 14, outline: 'none',
  };
  const labelMini = {
    fontSize: 11, fontWeight: 600, letterSpacing: 0.6, textTransform: 'uppercase',
    color: 'var(--muted)', marginBottom: 8,
  };
  const hasLink = link && link.groupId === groupId;

  return (
    <Sheet open={open} onClose={onClose} title={de ? 'Freund einladen' : 'Invite Friend'}>
      <div style={{ padding: '0 16px 28px' }}>
        {/* Mode toggle: E-Mail / Link / Share */}
        <div style={{
          display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 4,
          background: 'var(--surface)', border: '1px solid var(--border)',
          borderRadius: 12, padding: 4, marginBottom: 18,
        }}>
          {[['email', t('invite_mode_email')], ['link', t('invite_mode_link')], ['share', t('invite_mode_share')]].map(([m, label]) => (
            <button key={m} type="button" onClick={() => setMode(m)} style={{
              appearance: 'none', cursor: 'pointer', border: 0, padding: '9px 0', borderRadius: 9,
              background: mode === m ? 'var(--accent)' : 'transparent',
              color: mode === m ? 'var(--bg-deep)' : 'var(--muted)',
              fontFamily: 'Space Grotesk, system-ui', fontSize: 13, fontWeight: 700,
              transition: 'all 160ms ease',
            }}>{label}</button>
          ))}
        </div>

        {/* Shared group selector */}
        {groups.length > 1 && (
          <div style={{ marginBottom: 16 }}>
            <div style={labelMini}>{mode === 'email' ? (de ? 'In welche Gruppe?' : 'Which group?') : t('invite_which_group')}</div>
            <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
              {groups.map(g => {
                const on = groupId === g.id;
                return (
                  <button key={g.id} type="button" onClick={() => setGroupId(g.id)} style={{
                    appearance: 'none', cursor: 'pointer',
                    display: 'inline-flex', alignItems: 'center', gap: 6,
                    padding: '8px 12px', borderRadius: 999,
                    background: on ? `oklch(0.85 0.16 ${g.hue} / 0.16)` : 'var(--bg-deep)',
                    border: `1px solid ${on ? `oklch(0.85 0.16 ${g.hue} / 0.5)` : 'var(--border)'}`,
                    color: on ? `oklch(0.85 0.16 ${g.hue})` : 'var(--muted)',
                    fontFamily: 'Space Grotesk, system-ui', fontSize: 13, fontWeight: 600,
                  }}>
                    <span style={{ fontSize: 12 }}>{g.glyph}</span>
                    {window.groupName(g)}
                    {on && <span style={{ fontSize: 10, opacity: 0.85 }}>✓</span>}
                  </button>
                );
              })}
            </div>
          </div>
        )}

        {/* Link / Share modes */}
        {mode !== 'email' && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
            <div style={{ fontSize: 13, color: 'var(--muted)', lineHeight: 1.6 }}>
              {t('invite_link_desc')}
            </div>
            {hasLink && (
              <input readOnly value={link.url} onFocus={e => e.target.select()}
                style={{ ...inputStyle, fontFamily: 'JetBrains Mono, monospace', fontSize: 12 }} />
            )}
            {shareNote && (
              <div style={{ fontSize: 12, color: 'var(--muted)', lineHeight: 1.5 }}>{shareNote}</div>
            )}
            {mode === 'link' ? (
              <button type="button" onClick={handleCopy} disabled={linkBusy} style={{
                padding: '13px 0', borderRadius: 12, border: 0,
                background: linkBusy ? 'var(--surface)' : 'var(--accent)',
                color: linkBusy ? 'var(--muted)' : 'var(--bg-deep)',
                fontFamily: 'Space Grotesk, system-ui', fontSize: 15, fontWeight: 700,
                cursor: linkBusy ? 'not-allowed' : 'pointer',
              }}>
                {linkBusy ? t('invite_link_creating')
                  : copied ? t('invite_link_copied')
                  : hasLink ? t('invite_link_copy') : t('invite_link_create')}
              </button>
            ) : (
              <button type="button" onClick={handleShare} disabled={linkBusy} style={{
                padding: '13px 0', borderRadius: 12, border: 0,
                background: linkBusy ? 'var(--surface)' : 'var(--accent)',
                color: linkBusy ? 'var(--muted)' : 'var(--bg-deep)',
                fontFamily: 'Space Grotesk, system-ui', fontSize: 15, fontWeight: 700,
                cursor: linkBusy ? 'not-allowed' : 'pointer',
                display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
              }}>
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
                  <circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/>
                  <path d="M8.6 13.5l6.8 4M15.4 6.5l-6.8 4"/>
                </svg>
                {linkBusy ? t('invite_link_creating') : copied ? t('invite_link_copied') : t('invite_share_btn')}
              </button>
            )}
          </div>
        )}

        {/* Email mode */}
        {mode === 'email' && (
        <form onSubmit={handleInvite} style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          <div>
            <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: 0.6, textTransform: 'uppercase', color: 'var(--muted)', marginBottom: 6 }}>
              {de ? 'E-Mail des Freundes' : "Friend's Email"}
            </div>
            <input
              type="email" value={email} onChange={e => setEmail(e.target.value)}
              placeholder={de ? 'freund@email.de' : 'friend@email.com'}
              required style={inputStyle}
            />
          </div>

          {status === 'added' && (
            <div style={{
              padding: '12px 14px', borderRadius: 12,
              background: 'color-mix(in oklab, var(--accent) 12%, transparent)',
              border: '1px solid color-mix(in oklab, var(--accent) 30%, transparent)',
              color: 'var(--accent)', fontSize: 13,
            }}>
              ✓ {de ? 'Freund wurde direkt zur Gruppe hinzugefügt!' : 'Friend added to the group directly!'}
            </div>
          )}
          {status === 'invited' && (
            <div style={{
              padding: '12px 14px', borderRadius: 12,
              background: 'color-mix(in oklab, var(--violet) 12%, transparent)',
              border: '1px solid color-mix(in oklab, var(--violet) 30%, transparent)',
              color: 'var(--violet)', fontSize: 13, lineHeight: 1.5,
            }}>
              📨 {de
                ? 'Einladung gespeichert. Sobald sich dein Freund mit dieser E-Mail registriert, wird er automatisch hinzugefügt.'
                : 'Invite saved. As soon as your friend registers with this email, they\'ll be added automatically.'}
            </div>
          )}
          {status === 'error' && (
            <div style={{
              padding: '12px 14px', borderRadius: 12,
              background: 'color-mix(in oklab, var(--pink) 12%, transparent)',
              border: '1px solid color-mix(in oklab, var(--pink) 30%, transparent)',
              color: 'var(--pink)', fontSize: 13,
            }}>✕ {errMsg}</div>
          )}

          <button type="submit" disabled={status === 'loading' || !email.trim()} style={{
            padding: '13px 0', borderRadius: 12, border: 0,
            background: (status === 'loading' || !email.trim()) ? 'var(--surface)' : 'var(--accent)',
            color: (status === 'loading' || !email.trim()) ? 'var(--muted)' : 'var(--bg-deep)',
            fontFamily: 'Space Grotesk, system-ui', fontSize: 15, fontWeight: 700,
            cursor: (status === 'loading' || !email.trim()) ? 'not-allowed' : 'pointer',
          }}>
            {status === 'loading' ? (de ? 'Sende…' : 'Sending…') : (de ? 'Einladen' : 'Invite')}
          </button>
        </form>
        )}

        {/* Sent invites list */}
        {mode === 'email' && sentList.length > 0 && (
          <div style={{ marginTop: 24 }}>
            <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: 0.6, textTransform: 'uppercase', color: 'var(--muted)', marginBottom: 10 }}>
              {de ? 'Gesendete Einladungen' : 'Sent Invites'}
            </div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
              {sentList.map(inv => (
                <div key={inv.id} style={{
                  padding: '10px 12px', borderRadius: 10,
                  background: 'var(--surface)', border: '1px solid var(--border)',
                  display: 'flex', alignItems: 'center', gap: 10,
                }}>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                      {inv.toEmail}
                    </div>
                    <div style={{ fontSize: 11, color: 'var(--muted)', marginTop: 1 }}>
                      {inv.groupName}
                    </div>
                  </div>
                  <div style={{
                    fontSize: 10, fontWeight: 700, letterSpacing: 0.5,
                    textTransform: 'uppercase', padding: '3px 8px', borderRadius: 999,
                    background: inv.status === 'accepted'
                      ? 'color-mix(in oklab, var(--accent) 14%, transparent)'
                      : 'color-mix(in oklab, var(--violet) 14%, transparent)',
                    color: inv.status === 'accepted' ? 'var(--accent)' : 'var(--violet)',
                  }}>
                    {inv.status === 'accepted' ? (de ? 'Beigetreten' : 'Joined') : (de ? 'Ausstehend' : 'Pending')}
                  </div>
                </div>
              ))}
            </div>
          </div>
        )}
      </div>
    </Sheet>
  );
}

// New Group sheet — name + icon + color, then create
function SheetNewGroup({ open, onClose, onCreated }) {
  const t = window.t;
  const de = (window.__LANG || 'de') !== 'en';
  const GLYPHS = ['◉', '✦', '▲', '✺', '◆', '★', '♦', '⬡'];
  const HUES = [145, 220, 18, 295, 60, 330, 200, 100];
  const [name, setName]   = React.useState('');
  const [glyph, setGlyph] = React.useState(GLYPHS[0]);
  const [hue, setHue]     = React.useState(HUES[0]);
  const [busy, setBusy]   = React.useState(false);

  React.useEffect(() => {
    if (open) { setName(''); setGlyph(GLYPHS[0]); setHue(HUES[0]); setBusy(false); }
  }, [open]);

  const create = async () => {
    if (!name.trim() || busy) return;
    setBusy(true);
    const group = await window.fbCreateGroup({
      nameDe: name.trim(), nameEn: name.trim(), glyph, hue,
    });
    setBusy(false);
    onCreated?.(group);
    onClose();
  };

  const inputStyle = {
    width: '100%', padding: '12px 14px', boxSizing: 'border-box',
    background: 'var(--bg-deep)', border: '1px solid var(--border)',
    borderRadius: 12, color: 'var(--text)',
    fontFamily: 'Space Grotesk, system-ui', fontSize: 14, outline: 'none',
  };
  const labelStyle = {
    fontSize: 11, fontWeight: 600, letterSpacing: 0.6, textTransform: 'uppercase',
    color: 'var(--muted)', marginBottom: 6,
  };

  return (
    <Sheet open={open} onClose={onClose} title={t('neue_gruppe_title')}>
      <div style={{ padding: '0 16px 28px', display: 'flex', flexDirection: 'column', gap: 18 }}>
        {/* Preview */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
          <div style={{
            width: 52, height: 52, borderRadius: 14, flexShrink: 0,
            background: `oklch(0.3 0.06 ${hue})`,
            color: `oklch(0.88 0.16 ${hue})`,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            fontSize: 26, border: `1px solid oklch(0.85 0.16 ${hue} / 0.4)`,
          }}>{glyph}</div>
          <div style={{
            fontFamily: 'Space Grotesk, system-ui', fontSize: 18, fontWeight: 700,
            color: name.trim() ? 'var(--text)' : 'var(--muted)',
          }}>{name.trim() || t('gruppe_name_ph')}</div>
        </div>

        <div>
          <div style={labelStyle}>{t('gruppe_name')}</div>
          <input value={name} onChange={e => setName(e.target.value)}
            placeholder={t('gruppe_name_ph')} autoFocus style={inputStyle}
            onKeyDown={e => { if (e.key === 'Enter') create(); }} />
        </div>

        <div>
          <div style={labelStyle}>{t('gruppe_icon')}</div>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            {GLYPHS.map(g => {
              const on = g === glyph;
              return (
                <button key={g} type="button" onClick={() => setGlyph(g)} style={{
                  appearance: 'none', cursor: 'pointer',
                  width: 42, height: 42, borderRadius: 12, fontSize: 20,
                  background: on ? `oklch(0.3 0.06 ${hue})` : 'var(--bg-deep)',
                  color: on ? `oklch(0.88 0.16 ${hue})` : 'var(--muted)',
                  border: `1px solid ${on ? `oklch(0.85 0.16 ${hue} / 0.5)` : 'var(--border)'}`,
                }}>{g}</button>
              );
            })}
          </div>
        </div>

        <div>
          <div style={labelStyle}>{t('gruppe_farbe')}</div>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            {HUES.map(h => {
              const on = h === hue;
              return (
                <button key={h} type="button" onClick={() => setHue(h)} style={{
                  appearance: 'none', cursor: 'pointer',
                  width: 34, height: 34, borderRadius: '50%',
                  background: `oklch(0.7 0.16 ${h})`,
                  border: `2px solid ${on ? 'var(--text)' : 'transparent'}`,
                  boxShadow: on ? `0 0 0 2px var(--bg-deep)` : 'none',
                }} />
              );
            })}
          </div>
        </div>

        <Button full onClick={create} style={busy || !name.trim() ? {
          background: 'var(--surface)', color: 'var(--muted)',
          border: '1px solid var(--border)', boxShadow: 'none', cursor: 'not-allowed',
        } : {}}>
          {busy ? (de ? 'Erstelle…' : 'Creating…') : t('gruppe_erstellen')}
        </Button>
      </div>
    </Sheet>
  );
}

// Profile sheet — identity, log out, leave groups, push settings
function SheetProfile({ open, onClose, onOpenAddFriend, onGroupsChanged }) {
  const t = window.t;
  const de = (window.__LANG || 'de') !== 'en';
  const [, bump] = React.useReducer(x => x + 1, 0);
  const [push, setPush] = React.useState(!!window.__MY_PUSH);
  const [pushLead, setPushLead] = React.useState(window.__MY_PUSH_LEAD || 15);
  const [pushBlocked, setPushBlocked] = React.useState(false);

  // PWA install state
  const isStandalone = () =>
    (typeof window.matchMedia === 'function' && window.matchMedia('(display-mode: standalone)').matches) ||
    window.navigator.standalone === true;
  const [installed, setInstalled] = React.useState(isStandalone());
  const [canInstall, setCanInstall] = React.useState(!!window.__deferredInstallPrompt);
  const [gamertag, setGamertag] = React.useState(window.FRIENDS_BY_ID?.['me']?.gamertag || '');
  const [photoBusy, setPhotoBusy] = React.useState(false);
  const [photoError, setPhotoError] = React.useState(false);
  const [hideSingle, setHideSingle] = React.useState(window.__MY_HIDE_SINGLE !== false);
  const fileInputRef = React.useRef(null);

  React.useEffect(() => {
    if (open) {
      setHideSingle(window.__MY_HIDE_SINGLE !== false);
      setPush(!!window.__MY_PUSH);
      setPushLead(window.__MY_PUSH_LEAD || 15);
      setPushBlocked(typeof Notification !== 'undefined' && Notification.permission === 'denied');
      setInstalled(isStandalone());
      setCanInstall(!!window.__deferredInstallPrompt);
      setGamertag(window.FRIENDS_BY_ID?.['me']?.gamertag || '');
    }
  }, [open]);

  React.useEffect(() => {
    const onInstallable = () => setCanInstall(true);
    const onInstalled = () => { setInstalled(true); setCanInstall(false); };
    window.addEventListener('zocker-installable', onInstallable);
    window.addEventListener('zocker-installed', onInstalled);
    return () => {
      window.removeEventListener('zocker-installable', onInstallable);
      window.removeEventListener('zocker-installed', onInstalled);
    };
  }, []);

  const changePushLead = (min) => {
    setPushLead(min);
    window.fbSavePushLead?.(min);
    window.dispatchEvent(new Event('zocker-push-changed'));
  };

  const doInstall = async () => {
    const p = window.__deferredInstallPrompt;
    if (!p) return;
    p.prompt();
    try { await p.userChoice; } catch (_) {}
    window.__deferredInstallPrompt = null;
    setCanInstall(false);
  };

  const me = window.FRIENDS_BY_ID?.['me'] || { name: 'Zocker', gamertag: '' };
  // Shown in crew-priority order (highest first); reordering now lives on the
  // Crew screen ("Gruppen priorisieren"), so this list is read-only here.
  const myGroups = (window.GROUPS || [])
    .filter(g => g.memberIds.includes('me'))
    .sort((a, b) => window.crewRank(a.id) - window.crewRank(b.id));

  // Persist an edited gamertag (on blur / Enter). No-op when unchanged.
  const saveGamertag = () => {
    const val = gamertag.trim();
    if ((me.gamertag || '') === val) return;
    me.gamertag = val;
    window.fbSaveProfile?.({ gamertag: val });
    bump();
  };

  // The crew founder is its admin: they delete the group; everyone else leaves.
  const isAdmin = (g) => !!g.ownerUid && g.ownerUid === window.__FB_UID;

  // confirm = { kind: 'leave' | 'delete', group } while the modal is open
  const [confirm, setConfirm] = React.useState(null);

  const runConfirm = async () => {
    if (!confirm) return;
    const { kind, group } = confirm;
    setConfirm(null);
    if (kind === 'delete') await window.fbDeleteGroup?.(group.id);
    else await window.fbLeaveGroup?.(group.id);
    onGroupsChanged?.();
    bump();
  };

  const togglePush = async (next) => {
    if (next && typeof Notification !== 'undefined') {
      if (Notification.permission === 'denied') { setPushBlocked(true); return; }
      if (Notification.permission === 'default') {
        try {
          const res = await Notification.requestPermission();
          if (res === 'denied') { setPushBlocked(true); return; }
        } catch (_) { /* ignore */ }
      }
    }
    setPushBlocked(false);
    setPush(next);
    window.fbSavePushPref?.(next);
    window.dispatchEvent(new Event('zocker-push-changed'));
  };

  const onPickPhoto = async (e) => {
    const file = e.target.files && e.target.files[0];
    e.target.value = '';
    if (!file) return;
    setPhotoError(false);
    setPhotoBusy(true);
    try {
      const dataUrl = await window.compressImageFile(file, 256, 0.82);
      await window.fbSaveProfilePhoto?.(dataUrl);
      bump();
    } catch (_) {
      setPhotoError(true);
    } finally {
      setPhotoBusy(false);
    }
  };

  const removePhoto = async () => {
    setPhotoError(false);
    await window.fbSaveProfilePhoto?.(null);
    bump();
  };

  const toggleHideSingle = (next) => {
    setHideSingle(next);
    window.setHideSinglePlayer?.(next);
  };

  const logout = () => { onClose?.(); window.fbAuth?.signOut(); };

  const labelStyle = {
    fontSize: 11, fontWeight: 600, letterSpacing: 0.6, textTransform: 'uppercase',
    color: 'var(--muted)', marginBottom: 10,
  };

  return (
    <>
    <Sheet open={open} onClose={onClose} title={t('profile')} fullHeight>
      <div style={{ padding: '0 16px 28px' }}>
        {/* Identity */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 22 }}>
          <button type="button" onClick={() => fileInputRef.current?.click()}
            title={t('change_photo')} style={{
              appearance: 'none', cursor: 'pointer', padding: 0, border: 0,
              background: 'transparent', position: 'relative', flexShrink: 0,
              opacity: photoBusy ? 0.6 : 1,
            }}>
            <Avatar id="me" size={56} ring />
            {/* Camera badge */}
            <span style={{
              position: 'absolute', right: -2, bottom: -2,
              width: 22, height: 22, borderRadius: '50%',
              background: 'var(--accent)', color: 'var(--bg-deep)',
              border: '2px solid var(--surface)',
              display: 'flex', alignItems: 'center', justifyContent: 'center',
            }}>
              <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
                <path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/>
              </svg>
            </span>
          </button>
          <input ref={fileInputRef} type="file" accept="image/*" onChange={onPickPhoto}
            style={{ display: 'none' }} />
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{
              fontFamily: 'Space Grotesk, system-ui', fontSize: 20, fontWeight: 700,
              color: 'var(--text)', letterSpacing: -0.4,
              overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
            }}>{me.name}</div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 4, flexWrap: 'wrap' }}>
              <button type="button" onClick={() => fileInputRef.current?.click()} style={{
                appearance: 'none', cursor: 'pointer', background: 'transparent', border: 0, padding: 0,
                color: 'var(--accent)', fontFamily: 'Space Grotesk, system-ui',
                fontSize: 12, fontWeight: 600,
              }}>{photoBusy ? t('photo_uploading') : (me.photo ? t('change_photo') : t('upload_photo'))}</button>
              {me.photo && !photoBusy && (
                <button type="button" onClick={removePhoto} style={{
                  appearance: 'none', cursor: 'pointer', background: 'transparent', border: 0, padding: 0,
                  color: 'var(--muted)', fontFamily: 'Space Grotesk, system-ui',
                  fontSize: 12, fontWeight: 600,
                }}>{t('remove_photo')}</button>
              )}
            </div>
            {photoError && (
              <div style={{ fontSize: 11, color: 'var(--pink)', marginTop: 4 }}>{t('photo_error')}</div>
            )}
          </div>
        </div>

        {/* Gamertag (editable) */}
        <div style={labelStyle}>{t('gamertag')}</div>
        <input
          value={gamertag}
          onChange={(e) => setGamertag(e.target.value)}
          onBlur={saveGamertag}
          onKeyDown={(e) => { if (e.key === 'Enter') e.currentTarget.blur(); }}
          placeholder={t('gamertag_placeholder')}
          maxLength={32}
          style={{
            width: '100%', boxSizing: 'border-box', marginBottom: 24,
            padding: '12px 14px', borderRadius: 14,
            background: 'var(--surface)', border: '1px solid var(--border)',
            color: 'var(--text)',
            fontFamily: 'Space Grotesk, system-ui', fontSize: 14, outline: 'none',
          }}
        />

        {/* Language */}
        <div style={labelStyle}>{t('language_section')}</div>
        <div style={{
          background: 'var(--surface)', border: '1px solid var(--border)',
          borderRadius: 14, padding: 14, marginBottom: 24,
        }}>
          <div style={{
            fontFamily: 'Space Grotesk, system-ui', fontSize: 14, fontWeight: 600,
            color: 'var(--text)',
          }}>{t('language_label')}</div>
          <div style={{
            fontFamily: 'Space Grotesk, system-ui', fontSize: 12, color: 'var(--muted)',
            marginTop: 3, marginBottom: 12, lineHeight: 1.4,
          }}>{t('language_desc')}</div>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            {window.availableLanguages().map(l => {
              const on = (window.__LANG || 'de') === l.code;
              return (
                <button key={l.code} type="button"
                  onClick={() => { window.setLang(l.code); bump(); }} style={{
                    appearance: 'none', cursor: 'pointer',
                    display: 'inline-flex', alignItems: 'center', gap: 8,
                    padding: '9px 16px', borderRadius: 999,
                    background: on ? 'color-mix(in oklab, var(--accent) 16%, var(--bg-deep))' : 'var(--bg-deep)',
                    border: `1px solid ${on ? 'color-mix(in oklab, var(--accent) 45%, var(--border))' : 'var(--border)'}`,
                    color: on ? 'var(--accent)' : 'var(--text)',
                    fontFamily: 'Space Grotesk, system-ui', fontSize: 13, fontWeight: 600,
                  }}>
                  <span style={{ fontSize: 16, lineHeight: 1 }}>{l.flag}</span>
                  {l.label}
                </button>
              );
            })}
          </div>
        </div>

        {/* Game preferences */}
        <div style={labelStyle}>{t('hide_single_section')}</div>
        <div style={{
          background: 'var(--surface)', border: '1px solid var(--border)',
          borderRadius: 14, padding: 14, marginBottom: 24,
          display: 'flex', alignItems: 'center', gap: 12,
        }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{
              fontFamily: 'Space Grotesk, system-ui', fontSize: 14, fontWeight: 600,
              color: 'var(--text)',
            }}>{t('hide_single_label')}</div>
            <div style={{
              fontFamily: 'Space Grotesk, system-ui', fontSize: 12, color: 'var(--muted)',
              marginTop: 3, lineHeight: 1.4,
            }}>{t('hide_single_desc')}</div>
          </div>
          <Toggle on={hideSingle} onChange={toggleHideSingle} />
        </div>

        {/* Push settings */}
        <div style={labelStyle}>{t('push_section')}</div>
        <div style={{
          background: 'var(--surface)', border: '1px solid var(--border)',
          borderRadius: 14, padding: 14, marginBottom: 8,
          display: 'flex', alignItems: 'center', gap: 12,
        }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{
              fontFamily: 'Space Grotesk, system-ui', fontSize: 14, fontWeight: 600,
              color: 'var(--text)',
            }}>{t('push_label')}</div>
            <div style={{
              fontFamily: 'Space Grotesk, system-ui', fontSize: 12, color: 'var(--muted)',
              marginTop: 3, lineHeight: 1.4,
            }}>{t('push_desc')}</div>
          </div>
          <Toggle on={push} onChange={togglePush} />
        </div>
        {pushBlocked && (
          <div style={{
            fontSize: 12, color: 'var(--pink)', lineHeight: 1.5, marginBottom: 16,
            padding: '0 2px',
          }}>{t('push_blocked')}</div>
        )}

        {/* Reminder lead time (only relevant when push is on) */}
        {push && (
          <div style={{
            background: 'var(--surface)', border: '1px solid var(--border)',
            borderRadius: 14, padding: 14, marginTop: 8,
          }}>
            <div style={{
              fontFamily: 'Space Grotesk, system-ui', fontSize: 14, fontWeight: 600,
              color: 'var(--text)',
            }}>{t('push_lead_label')}</div>
            <div style={{
              fontFamily: 'Space Grotesk, system-ui', fontSize: 12, color: 'var(--muted)',
              marginTop: 3, marginBottom: 10, lineHeight: 1.4,
            }}>{t('push_lead_desc')}</div>
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
              {[5, 15, 30, 60].map(min => {
                const on = pushLead === min;
                return (
                  <button key={min} type="button" onClick={() => changePushLead(min)} style={{
                    appearance: 'none', cursor: 'pointer',
                    padding: '7px 14px', borderRadius: 999,
                    background: on ? 'color-mix(in oklab, var(--accent) 16%, var(--bg-deep))' : 'var(--bg-deep)',
                    border: `1px solid ${on ? 'color-mix(in oklab, var(--accent) 45%, var(--border))' : 'var(--border)'}`,
                    color: on ? 'var(--accent)' : 'var(--muted)',
                    fontFamily: 'Space Grotesk, system-ui', fontSize: 13, fontWeight: 600,
                  }}>{t('push_lead_min', min)}</button>
                );
              })}
            </div>
          </div>
        )}
        <div style={{ height: 24 }} />

        {/* Install as app */}
        <div style={labelStyle}>{t('install_section')}</div>
        <div style={{
          background: 'var(--surface)', border: '1px solid var(--border)',
          borderRadius: 14, padding: 14, marginBottom: 24,
          display: 'flex', alignItems: 'center', gap: 12,
        }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{
              fontFamily: 'Space Grotesk, system-ui', fontSize: 14, fontWeight: 600,
              color: 'var(--text)',
            }}>{t('install_label')}</div>
            <div style={{
              fontFamily: 'Space Grotesk, system-ui', fontSize: 12, color: 'var(--muted)',
              marginTop: 3, lineHeight: 1.4,
            }}>{installed ? t('install_done') : (canInstall ? t('install_desc') : t('install_ios'))}</div>
          </div>
          {!installed && canInstall && (
            <button type="button" onClick={doInstall} style={{
              appearance: 'none', cursor: 'pointer', flexShrink: 0,
              padding: '9px 16px', borderRadius: 999,
              background: 'var(--accent)', border: '1px solid var(--accent)',
              color: 'var(--bg-deep)',
              fontFamily: 'Space Grotesk, system-ui', fontSize: 13, fontWeight: 700,
            }}>{t('install_btn')}</button>
          )}
        </div>

        {/* Groups */}
        <div style={labelStyle}>{t('my_groups_section')}</div>
        {myGroups.length === 0 ? (
          <div style={{
            fontFamily: 'Space Grotesk, system-ui', fontSize: 13, color: 'var(--muted)',
            marginBottom: 24,
          }}>{t('no_groups_yet')}</div>
        ) : (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 24 }}>
            {myGroups.map((g) => (
              <div key={g.id} style={{
                background: 'var(--surface)', border: '1px solid var(--border)',
                borderRadius: 14, padding: 12,
                display: 'flex', alignItems: 'center', gap: 12,
              }}>
                <div style={{
                  width: 40, height: 40, borderRadius: 11, flexShrink: 0,
                  background: `oklch(0.3 0.06 ${g.hue})`,
                  color: `oklch(0.88 0.16 ${g.hue})`,
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  fontSize: 20, border: `1px solid oklch(0.85 0.16 ${g.hue} / 0.4)`,
                }}>
                  {g.glyph}
                </div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{
                    display: 'flex', alignItems: 'center', gap: 6,
                    fontFamily: 'Space Grotesk, system-ui', fontSize: 15, fontWeight: 600,
                    color: 'var(--text)',
                  }}>
                    <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{window.groupName(g)}</span>
                    {isAdmin(g) && (
                      <span style={{
                        flexShrink: 0,
                        fontFamily: 'JetBrains Mono, monospace', fontSize: 9, fontWeight: 700,
                        letterSpacing: 0.5, textTransform: 'uppercase',
                        color: 'var(--accent)',
                        background: 'color-mix(in oklab, var(--accent) 14%, transparent)',
                        border: '1px solid color-mix(in oklab, var(--accent) 30%, transparent)',
                        borderRadius: 999, padding: '2px 7px',
                      }}>{t('admin_badge')}</span>
                    )}
                  </div>
                  <div style={{
                    fontFamily: 'JetBrains Mono, monospace', fontSize: 11, color: 'var(--muted)',
                    marginTop: 2,
                  }}>{g.memberIds.length} · {t('mitglieder').toLowerCase()}</div>
                </div>
                <button type="button"
                  onClick={() => setConfirm({ kind: isAdmin(g) ? 'delete' : 'leave', group: g })}
                  style={{
                    appearance: 'none', cursor: 'pointer',
                    padding: '7px 14px', borderRadius: 999,
                    background: 'transparent',
                    border: '1px solid color-mix(in oklab, var(--pink) 35%, var(--border))',
                    color: 'var(--pink)',
                    fontFamily: 'Space Grotesk, system-ui', fontSize: 12, fontWeight: 700,
                    whiteSpace: 'nowrap',
                  }}>{isAdmin(g) ? t('delete_group') : t('leave_group')}</button>
              </div>
            ))}
          </div>
        )}

        {/* Account */}
        <div style={labelStyle}>{t('account')}</div>
        <Button variant="danger" full onClick={logout}>{t('logout')}</Button>
      </div>
    </Sheet>

    <window.ConfirmDialog
      open={!!confirm}
      danger
      title={confirm ? (confirm.kind === 'delete' ? t('delete_group') : t('leave_group')) : ''}
      message={confirm ? (confirm.kind === 'delete'
        ? t('delete_group_confirm', window.groupName(confirm.group))
        : t('leave_group_confirm', window.groupName(confirm.group))) : ''}
      confirmLabel={confirm ? (confirm.kind === 'delete' ? t('delete_group') : t('leave_group')) : ''}
      cancelLabel={t('cancel')}
      onConfirm={runConfirm}
      onCancel={() => setConfirm(null)}
    />
    </>
  );
}

Object.assign(window, { Sheet, SheetWochenplan, SheetTermine, SheetSession, SessionReadyCheck, SheetAddFriend, SheetNewGroup, SheetProfile });
