// ──────────────────────────────────────────────────────────────
// Creator Program → Event servers
// Track temporary game servers a blogger spun up for an event so we never
// forget to shut them down. Date-based reminders fire to Discord + browser
// (handled in notifications.checkEventServers). Synced via the Gist like
// everything else (storage.eventServers).
// ──────────────────────────────────────────────────────────────
const { useState: useSE, useMemo: useME } = React;

const EV_STATUS = {
  planned: { ru: "Запланирован", en: "Planned", color: "#5e63e6" },
  open:    { ru: "Открыт",       en: "Open",    color: "#1f9d55" },
  closed:  { ru: "Закрыт",       en: "Closed",  color: "#94a3b8" },
};

function evDaysLeft(endDate) {
  if (!endDate) return null;
  const d = new Date(endDate);
  if (isNaN(d.getTime())) return null;
  const today = new Date(); today.setHours(0, 0, 0, 0);
  d.setHours(0, 0, 0, 0);
  return Math.round((d.getTime() - today.getTime()) / 86400000);
}

// Format an event date that may be date-only ("2026-06-22") or include a time
// ("2026-06-22T14:30"). Shows HH:MM only when a time component is present.
function evFmt(d) {
  if (!d) return "—";
  const dt = new Date(d);
  if (isNaN(dt.getTime())) return d;
  const datePart = dt.toLocaleDateString("en-GB", { day: "2-digit", month: "short", year: "numeric" });
  const hasTime = typeof d === "string" && /T\d{2}:\d{2}/.test(d);
  if (!hasTime) return datePart;
  const timePart = dt.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: false });
  return `${datePart}, ${timePart}`;
}

function EventServerForm({ initial, creators, onSave, onCancel }) {
  const T = window.t || (s => s);
  const [f, setF] = useSE(initial || {
    creatorName: "", eventName: "", serverName: "", region: "",
    params: "", startDate: "", endDate: "", status: "planned", notes: "",
    adminLogin: "", adminPassword: "", adminUrl: "",
  });
  const set = (k, v) => setF(prev => ({ ...prev, [k]: v }));
  const inputStyle = { width: "100%", padding: "8px 10px", background: "var(--bg-app)", border: "1px solid var(--border-default)", borderRadius: 7, fontSize: 13 };
  const label = (t) => <label style={{ display: "block", fontSize: 11, fontWeight: 600, textTransform: "uppercase", letterSpacing: ".03em", color: "var(--text-tertiary)", marginBottom: 4 }}>{t}</label>;

  const creatorOptions = (creators || []).filter(c => !c.backlogAddedAt).map(c => c.name).filter(Boolean);

  return (
    <div className="modal-overlay" onClick={onCancel}>
      <div className="modal" onClick={(e) => e.stopPropagation()} style={{ width: 560, maxWidth: "94vw" }}>
        <h2 style={{ marginTop: 0 }}>🖥️ {initial ? T("Edit event server") : T("New event server")}</h2>
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
          <div style={{ gridColumn: "1 / -1" }}>
            {label(T("Blogger / creator"))}
            <input list="ev-creators" style={inputStyle} value={f.creatorName} onChange={(e) => set("creatorName", e.target.value)} placeholder={T("Who created the server")} />
            <datalist id="ev-creators">{creatorOptions.map(n => <option key={n} value={n} />)}</datalist>
            {(() => {
              const name = (f.creatorName || "").trim().toLowerCase();
              if (!name) return null;
              const ban = (window.storage?.penalties?.list?.() || []).find(p => (p.blogger || "").trim().toLowerCase() === name && (p.expiresAt || 0) > Date.now());
              if (!ban) return null;
              const daysLeft = Math.ceil((ban.expiresAt - Date.now()) / 86400000);
              return <div className="small" style={{ color: "var(--danger)", marginTop: 5, fontWeight: 600 }}>⛔ {T("This blogger is banned from servers")} — {daysLeft} {T("d left")}</div>;
            })()}
          </div>
          <div>
            {label(T("Event"))}
            <input style={inputStyle} value={f.eventName} onChange={(e) => set("eventName", e.target.value)} placeholder="OXIDE Survival Cup" />
          </div>
          <div>
            {label(T("Server name"))}
            <input style={inputStyle} value={f.serverName} onChange={(e) => set("serverName", e.target.value)} placeholder="[EU] OXIDE x Blogger" />
          </div>
          <div>
            {label(T("Region"))}
            <input style={inputStyle} value={f.region} onChange={(e) => set("region", e.target.value)} placeholder="EU / RU / NA" />
          </div>
          <div>
            {label(T("Status"))}
            <select style={inputStyle} value={f.status} onChange={(e) => set("status", e.target.value)}>
              {Object.entries(EV_STATUS).map(([k, v]) => <option key={k} value={k}>{(window.i18n?.getLang?.() === "ru" ? v.ru : v.en)}</option>)}
            </select>
          </div>
          <div>
            {label(T("Start date") + " 🟢")}
            <input type="datetime-local" style={inputStyle} value={f.startDate || ""} onChange={(e) => set("startDate", e.target.value)} />
          </div>
          <div>
            {label(T("Close date") + " 🔴")}
            <input type="datetime-local" style={inputStyle} value={f.endDate || ""} onChange={(e) => set("endDate", e.target.value)} />
          </div>
          <div style={{ gridColumn: "1 / -1" }}>
            {label(T("Server parameters"))}
            <textarea style={{ ...inputStyle, minHeight: 54, resize: "vertical", fontFamily: "inherit" }} value={f.params} onChange={(e) => set("params", e.target.value)} placeholder={T("Slots, map, mods, wipe schedule, password…")} />
          </div>
          <div style={{ gridColumn: "1 / -1" }}>
            {label("🔐 " + T("Admin data"))}
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
              <input style={inputStyle} value={f.adminLogin || ""} onChange={(e) => set("adminLogin", e.target.value)} placeholder={T("Admin login")} autoComplete="off" />
              <input style={inputStyle} value={f.adminPassword || ""} onChange={(e) => set("adminPassword", e.target.value)} placeholder={T("Admin password")} autoComplete="off" />
            </div>
            <input style={{ ...inputStyle, marginTop: 8 }} type="url" value={f.adminUrl || ""} onChange={(e) => set("adminUrl", e.target.value)} placeholder={T("Admin panel link")} autoComplete="off" />
          </div>
          <div style={{ gridColumn: "1 / -1" }}>
            {label(T("Notes"))}
            <textarea style={{ ...inputStyle, minHeight: 44, resize: "vertical", fontFamily: "inherit" }} value={f.notes} onChange={(e) => set("notes", e.target.value)} />
          </div>
        </div>
        <div className="row" style={{ justifyContent: "flex-end", gap: 8, marginTop: 16 }}>
          <button className="btn" onClick={onCancel}>{T("Cancel")}</button>
          <button className="btn primary" onClick={() => onSave(f)} disabled={!f.eventName && !f.serverName}>{T("Save")}</button>
        </div>
      </div>
    </div>
  );
}

function SecretField({ label, value }) {
  const T = window.t || (s => s);
  const [shown, setShown] = useSE(false);
  const [copied, setCopied] = useSE(false);
  if (!value) return null;
  const copy = () => {
    try { navigator.clipboard.writeText(value); setCopied(true); setTimeout(() => setCopied(false), 1500); } catch {}
  };
  return (
    <div className="row" style={{ alignItems: "center", gap: 8, marginTop: 6 }}>
      <span className="small muted" style={{ width: 70, flexShrink: 0 }}>{label}</span>
      <code style={{ flex: 1, fontFamily: "var(--font-mono)", fontSize: 13, padding: "5px 9px", background: "var(--bg-app)", border: "1px solid var(--border-subtle)", borderRadius: 6, letterSpacing: shown ? 0 : 2 }}>
        {shown ? value : "•".repeat(Math.min(12, String(value).length))}
      </code>
      <button className="btn sm ghost icon-only" onClick={() => setShown(s => !s)} data-tooltip={shown ? T("Hide") : T("Show")}>
        {shown ? "🙈" : "👁"}
      </button>
      {shown && (
        <button className="btn sm ghost icon-only" onClick={copy} data-tooltip={copied ? T("Copied!") : T("Copy")}>
          {copied ? <IconCheck size={13} /> : "⧉"}
        </button>
      )}
    </div>
  );
}

function EventServerDetail({ rec, onClose, onEdit }) {
  const T = window.t || (s => s);
  const isRu = (window.i18n?.getLang?.() === "ru");
  const fmtD = (d) => d ? new Date(d).toLocaleDateString("en-GB", { day: "2-digit", month: "short", year: "numeric" }) : "—";
  const st = EV_STATUS[rec.status] || EV_STATUS.planned;
  const dl = evDaysLeft(rec.endDate);
  const active = rec.status !== "closed";
  const Row = ({ k, v }) => v ? <div className="row" style={{ gap: 8, marginTop: 6 }}><span className="small muted" style={{ width: 110, flexShrink: 0 }}>{k}</span><span className="small" style={{ flex: 1, whiteSpace: "pre-wrap" }}>{v}</span></div> : null;
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()} style={{ width: 520, maxWidth: "94vw" }}>
        <div className="row" style={{ justifyContent: "space-between", alignItems: "flex-start", gap: 10 }}>
          <h2 style={{ margin: 0 }}>🖥️ {rec.eventName || rec.serverName || T("Event server")}</h2>
          <span className="small" style={{ color: "#fff", background: st.color, fontWeight: 700, padding: "3px 10px", borderRadius: 999, fontSize: 11, textTransform: "uppercase", flexShrink: 0 }}>{isRu ? st.ru : st.en}</span>
        </div>
        {rec.serverName && <div className="muted" style={{ marginTop: 4 }}>{rec.serverName}</div>}

        <Row k={T("Blogger / creator")} v={rec.creatorName} />
        <Row k={T("Region")} v={rec.region} />
        <Row k={T("Dates")} v={`🟢 ${evFmt(rec.startDate)}  →  🔴 ${evFmt(rec.endDate)}`} />
        {active && dl != null && (
          <Row k={T("Status")} v={dl < 0 ? `⚠️ ${T("overdue")} ${Math.abs(dl)} ${T("d")}` : dl === 0 ? `🔴 ${T("closes today!")}` : `${dl} ${T("d left")}`} />
        )}
        <Row k={T("Server parameters")} v={rec.params} />
        <Row k={T("Notes")} v={rec.notes} />

        {(rec.adminLogin || rec.adminPassword || rec.adminUrl) && (
          <div style={{ marginTop: 16, padding: "12px 14px", border: "1px solid var(--border-subtle)", borderRadius: 10, background: "var(--bg-surface)" }}>
            <div style={{ fontWeight: 700, fontSize: 13 }}>🔐 {T("Admin data")}</div>
            {rec.adminUrl && (
              <div className="row" style={{ alignItems: "center", gap: 8, marginTop: 6 }}>
                <span className="small muted" style={{ width: 70, flexShrink: 0 }}>{T("Panel")}</span>
                <a href={rec.adminUrl} target="_blank" rel="noopener noreferrer" className="small" style={{ flex: 1, color: "#2563eb", wordBreak: "break-all", display: "inline-flex", alignItems: "center", gap: 4 }}>
                  {rec.adminUrl} <IconExternal size={12} />
                </a>
              </div>
            )}
            <SecretField label={T("Login")} value={rec.adminLogin} />
            <SecretField label={T("Password")} value={rec.adminPassword} />
          </div>
        )}

        <div className="row" style={{ justifyContent: "flex-end", gap: 8, marginTop: 18 }}>
          <button className="btn" onClick={onClose}>{T("Close")}</button>
          <button className="btn primary" onClick={onEdit}><IconSettings size={13} /> {T("Edit")}</button>
        </div>
      </div>
    </div>
  );
}

const EV_RULES = {
  en: `🖥️ EVENT SERVER — REQUEST GUIDE

To spin up a server, please provide:
1. Event name
2. Responsible admin — who personally manages the server and adds players
3. Event region (RU events → RU region only)
4. Max players on the server (max: 110)
5. Resource rate (×)
6. Scrap rate (×)
7. Platform players will use (emulator / mobile — or all together)
8. Event start date
9. Event end date

📋 CONTENT — BEFORE & AFTER
• Before: tell us what content you plan to make on the server.
• After: send the result — the published content. For streams — the stream stats.
• A server is issued for content. No content on the way out = no servers next time.

⏳ DURATION & SHUTDOWN
• Max server lifetime is 1 week. It's for the event, not for free-roam play on high rates.
• On the close date the server is shut down automatically — regardless of whether you're "still playing". Hard cap is +12 hours grace if you didn't finish; after that it's off.
• Minimum gap between servers for one creator is 12 days. We track who took a server and when, so the same people don't get one back-to-back.

⚠️ IMPORTANT RULES
• Don't share your admin link — it's personal to you. The server is for YOU and your content, not to hand off to someone else to run.
• Use the server for the event only. Servers are real hardware and load — strictly as intended.
• Working around the rules (passing the server to a third party, "org" running it for others, etc.) = no more servers.

🎯 The main thing: we expect content made with the server.`,
  ru: `🖥️ ИВЕНТ-СЕРВЕР — ПАМЯТКА ПО ЗАПРОСУ

Для создания сервера нужно указать:
1. Название ивента
2. Ответственный админ — кто лично управляет сервером и добавляет людей
3. Регион ивента (если РУ — то только РУ)
4. Максимум игроков на сервере (макс: 110)
5. Множитель ресурсов (×)
6. Множитель скрапа (×)
7. На чём играют игроки (эмулятор / мобильные — можно всё вместе)
8. Дата старта ивента
9. Дата окончания ивента

📋 КОНТЕНТ — ДО И ПОСЛЕ
• До: расскажи, какой контент планируешь сделать на сервере.
• После: пришли результат — вышедший контент. Если был стрим — статистику стрима.
• Сервер выдаётся под контент. Нет контента на выходе — в следующий раз сервера не будет.

⏳ СРОК И ВЫКЛЮЧЕНИЕ
• Максимальный срок сервера — 1 неделя. Это под ивент, а не чтобы бегать и играть на х-ах просто так.
• В дату закрытия сервер вырубается автоматически — независимо от того, что вы ещё хотите. Максимум +12 часов запаса, если не успели; дальше — выключаем.
• Минимальный перерыв между серверами для одного креатора — 12 дней. Мы фиксируем, кто и когда брал сервер, чтобы одни и те же не брали подряд.

⚠️ ВАЖНЫЕ ПРАВИЛА
• Не шарь свою админскую ссылку — она персонально твоя. Сервер — для ТЕБЯ и твоего контента, а не чтобы передать кому-то рулить.
• Используй сервер только для ивента. Сервера — это железо и нагрузка, строго по назначению.
• Обход правил (передача сервера третьим лицам, когда им рулит «орг» для других и т.п.) = серверов больше не будет.

🎯 И главное: на выходе ждём контент с использованием сервера.`,
};

function RulesModal({ onClose }) {
  const T = window.t || (s => s);
  const [lang, setLang] = useSE((window.i18n?.getLang?.() === "ru") ? "ru" : "en");
  const text = lang === "ru" ? EV_RULES.ru : EV_RULES.en;
  const [copied, setCopied] = useSE(false);
  const copy = () => { try { navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 1800); } catch {} };
  const tabStyle = (on) => ({
    padding: "5px 12px", borderRadius: 7, fontSize: 12.5, fontWeight: 600, cursor: "pointer",
    border: "1px solid " + (on ? "var(--accent)" : "var(--border-default)"),
    background: on ? "var(--accent)" : "transparent",
    color: on ? "#fff" : "var(--text-secondary)",
  });
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()} style={{ width: 560, maxWidth: "94vw" }}>
        <div className="row" style={{ justifyContent: "space-between", alignItems: "center", marginBottom: 12 }}>
          <h2 style={{ margin: 0 }}>📋 {T("Event server rules")}</h2>
          <div className="row" style={{ gap: 6, alignItems: "center" }}>
            <button style={tabStyle(lang === "en")} onClick={() => setLang("en")}>EN</button>
            <button style={tabStyle(lang === "ru")} onClick={() => setLang("ru")}>RU</button>
            <button className="btn ghost icon-only" onClick={onClose}><IconClose size={16} /></button>
          </div>
        </div>
        <pre style={{
          whiteSpace: "pre-wrap", wordBreak: "break-word", fontFamily: "inherit", fontSize: 13.5,
          lineHeight: 1.6, margin: 0, padding: "14px 16px", background: "var(--bg-surface)",
          border: "1px solid var(--border-subtle)", borderRadius: 10, maxHeight: "60vh", overflowY: "auto",
        }}>{text}</pre>
        <div className="row" style={{ justifyContent: "flex-end", gap: 8, marginTop: 14 }}>
          <button className="btn" onClick={onClose}>{T("Close")}</button>
          <button className="btn primary" onClick={copy}>{copied ? <><IconCheck size={13} /> {T("Copied!")}</> : <>⧉ {T("Copy text")}</>}</button>
        </div>
      </div>
    </div>
  );
}

function EventCompletionModal({ server, onClose, onSubmit }) {
  const T = window.t || (s => s);
  const [rating, setRating] = useSE(0);
  const [hover, setHover] = useSE(0);
  const [comment, setComment] = useSE("");
  const [banDays, setBanDays] = useSE(0);
  const banOpts = [
    { d: 0, label: T("No ban") },
    { d: 7, label: "7 " + T("days") },
    { d: 30, label: "1 " + T("month") },
    { d: 180, label: "6 " + T("months") },
  ];
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()} style={{ width: 480, maxWidth: "94vw" }}>
        <h2 style={{ marginTop: 0 }}>🏁 {T("Event completed")}</h2>
        <div className="muted small" style={{ marginBottom: 12 }}>
          {server.eventName || server.serverName}{server.creatorName ? ` · ${server.creatorName}` : ""}
        </div>

        <label style={{ display: "block", fontSize: 11, fontWeight: 600, textTransform: "uppercase", letterSpacing: ".03em", color: "var(--text-tertiary)" }}>{T("How did the influencer do?")} (1–10)</label>
        <div className="fire-rating">
          {[1,2,3,4,5,6,7,8,9,10].map(n => (
            <span key={n} className={`fire-flame ${n <= (hover || rating) ? "lit" : ""}`}
              onClick={() => setRating(n)} onMouseEnter={() => setHover(n)} onMouseLeave={() => setHover(0)}>🔥</span>
          ))}
          {(hover || rating) > 0 && <span className="fire-rating-label">{hover || rating}/10</span>}
        </div>

        <label style={{ display: "block", fontSize: 11, fontWeight: 600, textTransform: "uppercase", letterSpacing: ".03em", color: "var(--text-tertiary)", marginBottom: 4 }}>{T("Comment")}</label>
        <textarea value={comment} onChange={(e) => setComment(e.target.value)} placeholder={T("How did it go? Content delivered? Any issues?")}
          style={{ width: "100%", minHeight: 70, resize: "vertical", padding: "8px 10px", background: "var(--bg-app)", border: "1px solid var(--border-default)", borderRadius: 7, fontSize: 13, fontFamily: "inherit" }} />

        <label style={{ display: "block", fontSize: 11, fontWeight: 600, textTransform: "uppercase", letterSpacing: ".03em", color: "var(--text-tertiary)", margin: "12px 0 6px" }}>⛔ {T("Server ban")}</label>
        <div className="row" style={{ gap: 6, flexWrap: "wrap" }}>
          {banOpts.map(o => (
            <button key={o.d} className={`btn sm ${banDays === o.d ? "primary" : ""}`}
              style={banDays === o.d && o.d > 0 ? { background: "var(--danger)", borderColor: "var(--danger)" } : {}}
              onClick={() => setBanDays(o.d)}>{o.label}</button>
          ))}
        </div>
        {banDays > 0 && <div className="small" style={{ color: "var(--danger)", marginTop: 6 }}>{T("This blogger won't be issued a server for")} {banDays} {T("days")}.</div>}

        <div className="row" style={{ justifyContent: "flex-end", gap: 8, marginTop: 18 }}>
          <button className="btn" onClick={onClose}>{T("Cancel")}</button>
          <button className="btn primary" onClick={() => onSubmit({ rating, comment: comment.trim(), banDays })}>
            <IconCheck size={13} /> {T("Close server")}
          </button>
        </div>
      </div>
    </div>
  );
}

function EventServersView({ creators }) {
  const T = window.t || (s => s);
  const isRu = (window.i18n?.getLang?.() === "ru");
  const [servers, setServers] = useSE(() => (window.storage?.eventServers?.list?.() || []).slice());
  const [editing, setEditing] = useSE(null);     // record or {} for new
  const [showForm, setShowForm] = useSE(false);
  const [dragId, setDragId] = useSE(null);
  const [dragOver, setDragOver] = useSE(null);
  const [detail, setDetail] = useSE(null);   // record being viewed
  const [completing, setCompleting] = useSE(null);  // server being completed/closed
  const [showRules, setShowRules] = useSE(false);
  const [histExpanded, setHistExpanded] = useSE(false);
  const [histFrom, setHistFrom] = useSE("");
  const [histTo, setHistTo] = useSE("");

  const refresh = () => setServers((window.storage?.eventServers?.list?.() || []).slice());

  React.useEffect(() => {
    const unsub = window.storage?.subscribe?.(refresh);
    return () => unsub && unsub();
  }, []);

  // Discord helper for event-server lifecycle events.
  const fmtD = (d) => d ? new Date(d).toLocaleDateString("en-GB", { day: "2-digit", month: "short", year: "numeric" }) : "—";
  const pingDiscord = (rec, kind) => {
    if (!window.discord?.hasWebhook?.()) return;
    const who = rec.creatorName ? ` · ${rec.creatorName}` : "";
    const name = rec.serverName || rec.eventName || "server";
    const fields = [
      rec.eventName ? { name: "Event", value: rec.eventName, inline: true } : null,
      rec.creatorName ? { name: "Blogger", value: rec.creatorName, inline: true } : null,
      rec.region ? { name: "Region", value: rec.region, inline: true } : null,
      (rec.startDate || rec.endDate) ? { name: "Dates", value: `🟢 ${evFmt(rec.startDate)} → 🔴 ${evFmt(rec.endDate)}`, inline: false } : null,
      rec.params ? { name: "Parameters", value: String(rec.params).slice(0, 300), inline: false } : null,
    ].filter(Boolean);
    const map = {
      created:  { title: `🖥️ New event server — ${name}`,        description: `A new event server was added${who}.`, color: 0x6f4cff },
      launched: { title: `🟢 Event server launched — ${name}`,     description: `Server${who} is now LIVE.`,            color: 0x22c55e },
      ended:    { title: `🔴 Event server ended — ${name}`,        description: `Server${who} was closed${who}. Make sure it's actually shut down.`, color: 0xef4444 },
    };
    const m = map[kind]; if (!m) return;
    window.discord.notifyCardEvent({ ...m, fields }).catch(() => {});
  };

  const save = (data) => {
    let rec;
    const wasStatus = editing && editing.id ? editing.status : null;
    if (editing && editing.id) {
      rec = window.storage.eventServers.update(editing.id, data);
      // status transitions via the form
      if (wasStatus !== "open" && data.status === "open") pingDiscord(rec, "launched");
      if (wasStatus !== "closed" && data.status === "closed") pingDiscord(rec, "ended");
    } else {
      rec = window.storage.eventServers.create(data);
      pingDiscord(rec, "created");
      if (data.status === "open") pingDiscord(rec, "launched");
    }
    setShowForm(false); setEditing(null);
    refresh();
    try { window.notifications?.checkEventServers?.(window.storage.eventServers.list()); } catch {}
  };
  const remove = (id) => { if (confirm(T("Delete this event server record?"))) { window.storage.eventServers.delete(id); refresh(); } };
  const setStatus = (id, status) => {
    const prev = window.storage.eventServers.list().find(s => s.id === id);
    if (prev && prev.status === status) return;
    // Closing a server → open the completion modal (rating + comment + optional ban)
    if (status === "closed" && prev?.status !== "closed") {
      setCompleting(prev);
      return;
    }
    const rec = window.storage.eventServers.update(id, { status });
    if (status === "open" && prev?.status !== "open") pingDiscord(rec, "launched");
    refresh();
  };

  // Finalize an event: save rating/comment on the server record, mirror the
  // comment into the matching creator's card notes, ping Discord, and optionally
  // issue a server-ban penalty.
  const completeEvent = ({ rating, comment, banDays }) => {
    const s = completing;
    if (!s) return;
    const rec = window.storage.eventServers.update(s.id, {
      status: "closed", closedAt: Date.now(), resultRating: rating || null, resultComment: comment || "",
    });
    // Mirror comment into the creator's card thread if the blogger matches one.
    if (s.creatorName) {
      const match = (window.storage.creators.list() || []).find(c => (c.name || "").trim().toLowerCase() === s.creatorName.trim().toLowerCase());
      if (match) {
        const me = window.storage.auth?.currentUser?.() || { name: "System", initials: "S", color: ["#6f4cff", "#3b82f6"] };
        const fires = rating ? " " + "🔥".repeat(rating) + ` (${rating}/10)` : "";
        const txt = `🖥️ ${T("Event completed")}: ${s.eventName || s.serverName || "server"}.${fires}${comment ? "\n" + comment : ""}${banDays ? `\n⛔ ${T("Server ban")}: ${banDays} ${T("days")}` : ""}`;
        const entry = { id: "cm_ev_" + Date.now(), authorName: me.name, authorInitials: me.initials, authorColor: me.color, text: txt, ts: Date.now(), replyTo: null };
        const cpatch = { comments: [...(match.comments || []), entry], resultRating: rating || match.resultRating };
        if (rating >= 9) cpatch.isTop = true;   // auto-promote to Top creators
        window.storage.creators.update(match.id, cpatch);
      }
    }
    // Discord
    if (window.discord?.hasWebhook?.()) {
      const who = s.creatorName ? ` · ${s.creatorName}` : "";
      const fields = [
        rating ? { name: "Rating", value: "🔥".repeat(rating) + ` ${rating}/10`, inline: true } : null,
        banDays ? { name: "⛔ Server ban", value: `${banDays} ${T("days")}`, inline: true } : null,
        comment ? { name: "Comment", value: String(comment).slice(0, 400), inline: false } : null,
      ].filter(Boolean);
      window.discord.notifyCardEvent({
        title: `🏁 Event completed — ${s.serverName || s.eventName || "server"}`,
        description: `Event${who} is done and the server was closed.`,
        color: banDays ? 0xef4444 : 0x22c55e, fields,
      }).catch(() => {});
    }
    // Issue penalty
    if (banDays && s.creatorName) {
      window.storage.penalties.create({
        blogger: s.creatorName, days: banDays,
        expiresAt: Date.now() + banDays * 86400000,
        reason: comment || "", rating: rating || null,
        eventName: s.eventName || "", serverName: s.serverName || "",
      });
    }
    setCompleting(null);
    refresh();
  };

  const openCount = servers.filter(s => s.status !== "closed").length;
  const overdueCount = servers.filter(s => s.status !== "closed" && (evDaysLeft(s.endDate) ?? 1) < 0).length;
  const closeTodayCount = servers.filter(s => s.status !== "closed" && evDaysLeft(s.endDate) === 0).length;

  const columns = useME(() => {
    const by = { planned: [], open: [], closed: [] };
    servers.forEach(s => { (by[s.status] || by.planned).push(s); });
    const score = (s) => {
      const dl = evDaysLeft(s.endDate);
      if (dl != null && dl < 0) return -2;
      if (dl === 0) return -1;
      return dl == null ? 9e9 : dl;
    };
    Object.keys(by).forEach(k => by[k].sort((a, b) => score(a) - score(b)));
    return by;
  }, [servers]);

  // Per-blogger history: when did each creator last get a server, how many
  // times, and the date range of their most recent one.
  const byBlogger = useME(() => {
    const map = {};
    servers.forEach(s => {
      const name = (s.creatorName || "").trim() || "—";
      if (!map[name]) map[name] = { name, count: 0, servers: [], lastStart: 0 };
      map[name].count++;
      map[name].servers.push(s);
      const t = s.startDate ? new Date(s.startDate).getTime() : (s.createdAt || 0);
      if (t > map[name].lastStart) { map[name].lastStart = t; map[name].lastServer = s; }
    });
    return Object.values(map).sort((a, b) => (b.lastStart || 0) - (a.lastStart || 0));
  }, [servers]);

  return (
    <div>
      {/* Currently-banned bloggers — prominent strip so you never issue a server to a banned one */}
      {(() => {
        const now = Date.now();
        const active = (window.storage?.penalties?.list?.() || [])
          .filter(p => (p.expiresAt || 0) > now)
          .sort((a, b) => (a.expiresAt || 0) - (b.expiresAt || 0));
        if (active.length === 0) return null;
        return (
          <div style={{ marginBottom: 14, padding: "11px 14px", background: "rgba(239,68,68,0.10)", border: "1px solid rgba(239,68,68,0.4)", borderRadius: 10 }}>
            <div className="row" style={{ alignItems: "center", gap: 8, marginBottom: 8 }}>
              <span style={{ fontWeight: 800, color: "var(--danger)" }}>⛔ {T("Banned from servers")}</span>
              <span className="small muted">{active.length}</span>
              <a href="#penalties" className="small" style={{ marginLeft: "auto", color: "var(--danger)" }}>{T("Manage")} →</a>
            </div>
            <div className="row" style={{ gap: 8, flexWrap: "wrap" }}>
              {active.map(p => {
                const daysLeft = Math.ceil((p.expiresAt - now) / 86400000);
                return (
                  <span key={p.id} style={{ display: "inline-flex", alignItems: "center", gap: 6, background: "var(--bg-app)", border: "1px solid rgba(239,68,68,0.4)", borderRadius: 999, padding: "4px 11px", fontSize: 12.5 }}>
                    <b>{p.blogger}</b>
                    <span style={{ color: "var(--danger)", fontWeight: 700 }}>{daysLeft} {T("d left")}</span>
                  </span>
                );
              })}
            </div>
          </div>
        );
      })()}

      {/* Summary cards */}
      <div className="metrics-grid" style={{ marginBottom: 14 }}>
        <MetricTile label={T("Active servers")} value={openCount} />
        <MetricTile label={T("Close today")} value={closeTodayCount} trend={closeTodayCount ? T("act now") : null} trendDir={closeTodayCount ? "down" : null} />
        <MetricTile label={T("Overdue (not closed)")} value={overdueCount} trend={overdueCount ? T("close them!") : null} trendDir={overdueCount ? "down" : null} />
        <div className="metric" style={{ display: "flex", alignItems: "center", justifyContent: "center" }}>
          <button className="btn primary" onClick={() => { setEditing(null); setShowForm(true); }}>
            <IconPlus size={14} /> {T("New event server")}
          </button>
        </div>
      </div>

      <div className="row" style={{ alignItems: "center", gap: 10, marginBottom: 12 }}>
        <div className="small muted" style={{ flex: 1 }}>
          {T("Date-based reminders fire to Discord and your browser so temporary servers get closed on time.")}
        </div>
        <button className="btn" onClick={() => setShowRules(true)}>📋 {T("Rules")}</button>
      </div>

      {/* Per-blogger history dashboard */}
      {byBlogger.length > 0 && (() => {
        // Apply calendar filter (by last-received date) only when expanded.
        const fromT = histFrom ? new Date(histFrom).getTime() : null;
        const toT = histTo ? new Date(histTo).getTime() + 86400000 - 1 : null;
        const filtered = (histExpanded && (fromT || toT))
          ? byBlogger.filter(b => b.lastStart && (fromT == null || b.lastStart >= fromT) && (toT == null || b.lastStart <= toT))
          : byBlogger;
        const visible = histExpanded ? filtered : filtered.slice(0, 6);
        const hiddenCount = byBlogger.length - 6;
        return (
        <div className="section-block" style={{ marginBottom: 14 }}>
          <div className="section-block-title">
            <span className="icon"><IconKanban size={14} /></span>
            {T("Server history by blogger")}
            <span className="meta">{byBlogger.length}</span>
          </div>

          {histExpanded && (
            <div className="row" style={{ alignItems: "center", gap: 8, margin: "4px 0 12px", flexWrap: "wrap" }}>
              <span className="small muted">{T("Last received between")}</span>
              <input type="date" value={histFrom} onChange={(e) => setHistFrom(e.target.value)}
                style={{ padding: "5px 8px", background: "var(--bg-app)", border: "1px solid var(--border-default)", borderRadius: 6, fontSize: 12 }} />
              <span className="small muted">—</span>
              <input type="date" value={histTo} onChange={(e) => setHistTo(e.target.value)}
                style={{ padding: "5px 8px", background: "var(--bg-app)", border: "1px solid var(--border-default)", borderRadius: 6, fontSize: 12 }} />
              {(histFrom || histTo) && (
                <button className="btn sm ghost" onClick={() => { setHistFrom(""); setHistTo(""); }}>{T("Reset")}</button>
              )}
              <span className="small muted" style={{ marginLeft: "auto" }}>{filtered.length} / {byBlogger.length}</span>
            </div>
          )}

          <div style={{ overflowX: "auto" }}>
            <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
              <thead>
                <tr style={{ textAlign: "left", color: "var(--text-tertiary)", fontSize: 11, textTransform: "uppercase", letterSpacing: ".04em" }}>
                  <th style={{ padding: "6px 8px", fontWeight: 600 }}>{T("Blogger / creator")}</th>
                  <th style={{ padding: "6px 8px", fontWeight: 600, textAlign: "center" }}>{T("Servers")}</th>
                  <th style={{ padding: "6px 8px", fontWeight: 600 }}>{T("Last server")}</th>
                  <th style={{ padding: "6px 8px", fontWeight: 600 }}>{T("Dates")}</th>
                  <th style={{ padding: "6px 8px", fontWeight: 600 }}>{T("Last received")}</th>
                </tr>
              </thead>
              <tbody>
                {visible.map(b => {
                  const last = b.lastServer;
                  const daysSince = b.lastStart ? Math.floor((Date.now() - b.lastStart) / 86400000) : null;
                  const lastDates = last ? `🟢 ${evFmt(last.startDate)} → 🔴 ${evFmt(last.endDate)}` : "—";
                  return (
                    <tr key={b.name} style={{ borderTop: "1px solid var(--border-subtle)" }}>
                      <td style={{ padding: "9px 8px", fontWeight: 600 }}>{b.name}</td>
                      <td style={{ padding: "9px 8px", textAlign: "center" }}>
                        <span style={{ background: "var(--bg-app)", border: "1px solid var(--border-subtle)", borderRadius: 999, padding: "2px 9px", fontWeight: 700 }}>{b.count}</span>
                      </td>
                      <td style={{ padding: "9px 8px" }}>{last ? (last.eventName || last.serverName || "—") : "—"}</td>
                      <td style={{ padding: "9px 8px", whiteSpace: "nowrap", color: "var(--text-secondary)" }}>{lastDates}</td>
                      <td style={{ padding: "9px 8px", whiteSpace: "nowrap" }}>
                        {b.lastStart ? (
                          <span>{evFmt(last && last.startDate ? last.startDate : new Date(b.lastStart).toISOString())}{daysSince != null && <span className="muted"> · {daysSince === 0 ? T("today") : `${daysSince} ${T("d ago")}`}</span>}</span>
                        ) : "—"}
                      </td>
                    </tr>
                  );
                })}
                {visible.length === 0 && (
                  <tr><td colSpan={5} className="small muted" style={{ padding: "12px 8px" }}>{T("Nothing in this range.")}</td></tr>
                )}
              </tbody>
            </table>
          </div>

          {(byBlogger.length > 6 || histExpanded) && (
            <div style={{ textAlign: "center", marginTop: 10 }}>
              <button className="btn sm" onClick={() => { setHistExpanded(v => !v); if (histExpanded) { setHistFrom(""); setHistTo(""); } }}>
                {histExpanded ? T("Show less") : `${T("Show all")} (${byBlogger.length})`}
              </button>
            </div>
          )}
        </div>
        );
      })()}

      {/* Kanban board by status */}
      <div className="ev-board" style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 12, alignItems: "start" }}>
        {Object.keys(EV_STATUS).map(colKey => {
          const meta = EV_STATUS[colKey];
          const cards = columns[colKey] || [];
          return (
            <div
              key={colKey}
              className="ev-col"
              onDragOver={(e) => { e.preventDefault(); setDragOver(colKey); }}
              onDragLeave={() => setDragOver(d => d === colKey ? null : d)}
              onDrop={(e) => { e.preventDefault(); if (dragId) setStatus(dragId, colKey); setDragId(null); setDragOver(null); }}
              style={{
                background: "var(--bg-surface)",
                border: `1px solid ${dragOver === colKey ? meta.color : "var(--border-subtle)"}`,
                borderRadius: 12, padding: 10, minHeight: 120,
                transition: "border-color .15s",
              }}
            >
              <div className="row" style={{ alignItems: "center", gap: 8, marginBottom: 10, padding: "2px 4px" }}>
                <span style={{ width: 9, height: 9, borderRadius: "50%", background: meta.color, display: "inline-block" }} />
                <b style={{ fontSize: 13 }}>{isRu ? meta.ru : meta.en}</b>
                <span className="small muted">{cards.length}</span>
                <button className="btn sm ghost icon-only" style={{ marginLeft: "auto" }} data-tooltip={T("New event server")}
                  onClick={() => { setEditing({ status: colKey }); setShowForm(true); }}>
                  <IconPlus size={13} />
                </button>
              </div>
              <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
                {cards.length === 0 && <div className="small muted" style={{ padding: "8px 4px" }}>{T("Empty")}</div>}
                {cards.map(s => {
                  const dl = evDaysLeft(s.endDate);
                  const active = s.status !== "closed";
                  const overdue = active && dl != null && dl < 0;
                  const today = active && dl === 0;
                  const accent = overdue ? "#f59e0b" : today ? "#ef4444" : meta.color;
                  return (
                    <div
                      key={s.id}
                      draggable
                      onDragStart={() => setDragId(s.id)}
                      onDragEnd={() => { setDragId(null); setDragOver(null); }}
                      onClick={(e) => { if (e.target.closest("button")) return; setDetail(s); }}
                      className="ev-card"
                      style={{
                        background: "var(--bg-app)",
                        border: "1px solid var(--border-subtle)",
                        borderLeft: `3px solid ${accent}`,
                        borderRadius: 8, padding: "9px 11px", cursor: "pointer",
                        opacity: dragId === s.id ? 0.5 : 1,
                      }}
                    >
                      <div className="row" style={{ justifyContent: "space-between", alignItems: "flex-start", gap: 6 }}>
                        <div style={{ fontWeight: 700, fontSize: 13, lineHeight: 1.25 }}>{s.eventName || s.serverName || "—"}</div>
                        <div className="row" style={{ gap: 2, flexShrink: 0 }}>
                          <button className="btn sm ghost icon-only" onClick={() => { setEditing(s); setShowForm(true); }} data-tooltip={T("Edit")}><IconSettings size={12} /></button>
                          <button className="btn sm ghost icon-only" onClick={() => remove(s.id)} data-tooltip={T("Delete")} style={{ color: "var(--danger)" }}><IconTrash size={12} /></button>
                        </div>
                      </div>
                      {s.serverName && s.eventName && <div className="small muted" style={{ marginTop: 2 }}>{s.serverName}</div>}
                      <div className="small muted" style={{ marginTop: 3 }}>
                        {s.creatorName ? `👤 ${s.creatorName}` : ""}{s.region ? `  ·  ${s.region}` : ""}
                      </div>
                      {s.params && <div className="small" style={{ marginTop: 5, color: "var(--text-secondary)", whiteSpace: "pre-wrap" }}>{s.params}</div>}
                      <div className="small" style={{ marginTop: 6, color: "var(--text-tertiary)" }}>
                        🟢 {evFmt(s.startDate)} → 🔴 {evFmt(s.endDate)}
                      </div>
                      {active && dl != null && (
                        <div className="small" style={{ marginTop: 4, fontWeight: 700, color: accent }}>
                          {overdue ? `⚠️ ${T("overdue")} ${Math.abs(dl)} ${T("d")}` : today ? `🔴 ${T("closes today!")}` : `${dl} ${T("d left")}`}
                        </div>
                      )}
                      <div className="row" style={{ gap: 4, marginTop: 8 }}>
                        {colKey !== "open" && colKey !== "closed" && (
                          <button className="btn sm" onClick={() => setStatus(s.id, "open")} style={{ flex: 1 }}>🟢 {T("Launch")}</button>
                        )}
                        {colKey !== "closed" && (
                          <button className="btn sm" onClick={() => setStatus(s.id, "closed")} style={{ flex: 1 }}><IconCheck size={11} /> {T("Close")}</button>
                        )}
                        {colKey === "closed" && (
                          <button className="btn sm ghost" onClick={() => setStatus(s.id, "open")} style={{ flex: 1 }}><IconRefresh size={11} /> {T("Reopen")}</button>
                        )}
                      </div>
                    </div>
                  );
                })}
              </div>
            </div>
          );
        })}
      </div>

      {showRules && <RulesModal onClose={() => setShowRules(false)} />}

      {completing && (
        <EventCompletionModal
          server={completing}
          onClose={() => setCompleting(null)}
          onSubmit={completeEvent}
        />
      )}

      {detail && (
        <EventServerDetail
          rec={detail}
          onClose={() => setDetail(null)}
          onEdit={() => { setEditing(detail); setShowForm(true); setDetail(null); }}
        />
      )}

      {showForm && (
        <EventServerForm
          initial={editing}
          creators={creators}
          onSave={save}
          onCancel={() => { setShowForm(false); setEditing(null); }}
        />
      )}
    </div>
  );
}

Object.assign(window, { EventServersView });

// ──────────────────────────────────────────────────────────────
// Penalties — bloggers banned from getting an event server.
// ──────────────────────────────────────────────────────────────
function PenaltyForm({ creators, onSave, onCancel }) {
  const T = window.t || (s => s);
  const [blogger, setBlogger] = useSE("");
  const [days, setDays] = useSE(7);
  const [reason, setReason] = useSE("");
  const inputStyle = { width: "100%", padding: "8px 10px", background: "var(--bg-app)", border: "1px solid var(--border-default)", borderRadius: 7, fontSize: 13 };
  const opts = (creators || []).filter(c => !c.backlogAddedAt).map(c => c.name).filter(Boolean);
  return (
    <div className="modal-overlay" onClick={onCancel}>
      <div className="modal" onClick={(e) => e.stopPropagation()} style={{ width: 440, maxWidth: "94vw" }}>
        <h2 style={{ marginTop: 0 }}>⛔ {T("New penalty")}</h2>
        <label style={{ display: "block", fontSize: 11, fontWeight: 600, textTransform: "uppercase", color: "var(--text-tertiary)", marginBottom: 4 }}>{T("Blogger / creator")}</label>
        <input list="pen-creators" style={inputStyle} value={blogger} onChange={(e) => setBlogger(e.target.value)} />
        <datalist id="pen-creators">{opts.map(n => <option key={n} value={n} />)}</datalist>
        <label style={{ display: "block", fontSize: 11, fontWeight: 600, textTransform: "uppercase", color: "var(--text-tertiary)", margin: "12px 0 6px" }}>{T("Ban length")}</label>
        <div className="row" style={{ gap: 6, flexWrap: "wrap" }}>
          {[{d:7,l:"7 "+T("days")},{d:30,l:"1 "+T("month")},{d:180,l:"6 "+T("months")}].map(o => (
            <button key={o.d} className={`btn sm ${days === o.d ? "primary" : ""}`} style={days === o.d ? { background: "var(--danger)", borderColor: "var(--danger)" } : {}} onClick={() => setDays(o.d)}>{o.l}</button>
          ))}
        </div>
        <label style={{ display: "block", fontSize: 11, fontWeight: 600, textTransform: "uppercase", color: "var(--text-tertiary)", margin: "12px 0 4px" }}>{T("Reason")}</label>
        <textarea style={{ ...inputStyle, minHeight: 56, resize: "vertical", fontFamily: "inherit" }} value={reason} onChange={(e) => setReason(e.target.value)} />
        <div className="row" style={{ justifyContent: "flex-end", gap: 8, marginTop: 16 }}>
          <button className="btn" onClick={onCancel}>{T("Cancel")}</button>
          <button className="btn primary" onClick={() => onSave({ blogger: blogger.trim(), days, reason: reason.trim(), expiresAt: Date.now() + days * 86400000 })} disabled={!blogger.trim()}>{T("Issue ban")}</button>
        </div>
      </div>
    </div>
  );
}

function PenaltiesView({ creators }) {
  const T = window.t || (s => s);
  const [pens, setPens] = useSE(() => (window.storage?.penalties?.list?.() || []).slice());
  const [showForm, setShowForm] = useSE(false);
  const refresh = () => setPens((window.storage?.penalties?.list?.() || []).slice());
  React.useEffect(() => {
    const unsub = window.storage?.subscribe?.(refresh);
    return () => unsub && unsub();
  }, []);
  const add = (data) => { window.storage.penalties.create(data); setShowForm(false); refresh(); };
  const lift = (id) => { if (confirm(T("Lift this ban?"))) { window.storage.penalties.delete(id); refresh(); } };

  const now = Date.now();
  const fmtD = (d) => d ? new Date(d).toLocaleDateString("en-GB", { day: "2-digit", month: "short", year: "numeric" }) : "—";
  const sorted = pens.slice().sort((a, b) => {
    const aa = (a.expiresAt || 0) > now ? 0 : 1, bb = (b.expiresAt || 0) > now ? 0 : 1;
    if (aa !== bb) return aa - bb;
    return (b.expiresAt || 0) - (a.expiresAt || 0);
  });
  const activeCount = pens.filter(p => (p.expiresAt || 0) > now).length;

  return (
    <div>
      <div className="metrics-grid" style={{ marginBottom: 14 }}>
        <MetricTile label={T("Active bans")} value={activeCount} />
        <MetricTile label={T("Total penalties")} value={pens.length} />
        <div className="metric" style={{ display: "flex", alignItems: "center", justifyContent: "center" }}>
          <button className="btn primary" onClick={() => setShowForm(true)}><IconPlus size={14} /> {T("New penalty")}</button>
        </div>
      </div>

      <div className="section-block">
        <div className="section-block-title">
          <span className="icon">⛔</span>
          {T("Server bans")}
          <span className="meta">{pens.length}</span>
        </div>
        <div className="small muted" style={{ marginBottom: 10 }}>{T("Bloggers who won't be issued an event server until the ban expires.")}</div>
        {sorted.length === 0 ? (
          <div className="small muted" style={{ padding: "16px 2px" }}>{T("No penalties.")}</div>
        ) : (
          <div style={{ overflowX: "auto" }}>
            <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
              <thead>
                <tr style={{ textAlign: "left", color: "var(--text-tertiary)", fontSize: 11, textTransform: "uppercase", letterSpacing: ".04em" }}>
                  <th style={{ padding: "6px 8px", fontWeight: 600 }}>{T("Blogger / creator")}</th>
                  <th style={{ padding: "6px 8px", fontWeight: 600 }}>{T("Ban length")}</th>
                  <th style={{ padding: "6px 8px", fontWeight: 600 }}>{T("Status")}</th>
                  <th style={{ padding: "6px 8px", fontWeight: 600 }}>{T("Reason")}</th>
                  <th style={{ padding: "6px 8px", fontWeight: 600, textAlign: "right" }}></th>
                </tr>
              </thead>
              <tbody>
                {sorted.map(p => {
                  const active = (p.expiresAt || 0) > now;
                  const daysLeft = active ? Math.ceil((p.expiresAt - now) / 86400000) : 0;
                  return (
                    <tr key={p.id} style={{ borderTop: "1px solid var(--border-subtle)", background: active ? "rgba(239,68,68,0.08)" : "transparent" }}>
                      <td style={{ padding: "10px 8px", fontWeight: 700 }}>{p.blogger}{p.rating ? <span className="muted small" style={{ marginLeft: 6 }}>{"🔥".repeat(p.rating)}</span> : null}</td>
                      <td style={{ padding: "10px 8px", whiteSpace: "nowrap" }}>{p.days} {T("days")}<div className="small muted">{fmtD(p.issuedAt)} → {fmtD(p.expiresAt)}</div></td>
                      <td style={{ padding: "10px 8px" }}>
                        {active
                          ? <span className="small" style={{ color: "#fff", background: "var(--danger)", fontWeight: 700, padding: "2px 9px", borderRadius: 999, fontSize: 10, textTransform: "uppercase" }}>{T("Banned")} · {daysLeft} {T("d left")}</span>
                          : <span className="small" style={{ color: "var(--text-tertiary)", fontWeight: 700, padding: "2px 9px", borderRadius: 999, fontSize: 10, textTransform: "uppercase", border: "1px solid var(--border-subtle)" }}>{T("Expired")}</span>}
                      </td>
                      <td style={{ padding: "10px 8px", color: "var(--text-secondary)" }}>{p.reason || (p.eventName ? `${T("Event")}: ${p.eventName}` : "—")}</td>
                      <td style={{ padding: "10px 8px", textAlign: "right" }}>
                        <button className="btn sm ghost" onClick={() => lift(p.id)} data-tooltip={T("Lift ban")}>{T("Lift")}</button>
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        )}
      </div>

      {showForm && <PenaltyForm creators={creators} onSave={add} onCancel={() => setShowForm(false)} />}
    </div>
  );
}

Object.assign(window, { PenaltiesView });
