/* Page components — Home, Security, Servers, Documentation */ const { useState, useMemo } = React; /* ---------- shared ---------- */ const Section = ({ id, title, sub, children, kicker, className = "" }) => (
{kicker && (
{kicker}
)} {title && (

{title}

)} {sub && (

{sub}

)} {children}
); const Btn = ({ kind = "primary", as = "button", href, children, icon, ...rest }) => { const Comp = as; const base = "inline-flex items-center gap-2 px-5 py-3 text-[13px] uppercase tracking-[0.2em] transition-all duration-200 select-none"; const styles = kind === "primary" ? { background: "var(--blood)", color: "#f5e7e7", border: "1px solid var(--blood-bright)", boxShadow: "0 0 0 0 rgba(139,0,0,0)", } : kind === "ghost" ? { background: "transparent", color: "var(--text)", border: "1px solid var(--border-strong)", } : { background: "var(--panel)", color: "var(--text)", border: "1px solid var(--border)", }; return ( { if (kind === "primary") e.currentTarget.style.boxShadow = "0 0 24px 0 rgba(139,0,0,0.55)"; if (kind === "ghost") { e.currentTarget.style.borderColor = "var(--blood)"; e.currentTarget.style.color = "#fff"; } }} onMouseLeave={(e) => { if (kind === "primary") e.currentTarget.style.boxShadow = "0 0 0 0 rgba(139,0,0,0)"; if (kind === "ghost") { e.currentTarget.style.borderColor = "var(--border-strong)"; e.currentTarget.style.color = "var(--text)"; } }} {...rest} > {icon} {children} ); }; const Card = ({ children, className = "", hoverable = true }) => { const [hov, setHov] = useState(false); return (
hoverable && setHov(true)} onMouseLeave={() => hoverable && setHov(false)} className={"rounded-sm transition-all duration-300 " + className} style={{ background: "var(--panel)", border: "1px solid", borderColor: hov ? "var(--blood-deep)" : "var(--border)", boxShadow: hov ? "0 0 0 1px rgba(139,0,0,0.18) inset" : "none", }} > {children}
); }; /* ---------- HOME ---------- */ const StackTable = ({ t }) => { const [open, setOpen] = useState(false); return (
{t.home.stackCols.component}
{t.home.stackCols.tech}
{t.home.stackCols.purpose}
{t.home.stack.map((row, i) => (
{row[0]}
{row[1]}
{row[2]}
))}
); }; const HomePage = ({ t, lang, onGo, theme, showHand = true, bannerSrc = null }) => { const [tab, setTab] = useState("win"); const [open, setOpen] = useState(false); const art = window.ARTIFACTS[tab]; const verifyCmd = window.VERIFY_CMDS[tab]; // Live status of FEAR servers — polled every 30s from /api/status.json const [statusMap, setStatusMap] = useState({}); // id -> { status, ping_ms } useEffect(() => { let cancelled = false; const fetchStatus = async () => { try { const r = await fetch("/api/status.json", { cache: "no-store" }); if (!r.ok) return; const data = await r.json(); if (cancelled) return; const map = {}; for (const s of (data.servers || [])) map[s.id] = s; setStatusMap(map); } catch (e) { /* ignore — show as checking */ } }; fetchStatus(); const id = setInterval(fetchStatus, 30000); return () => { cancelled = true; clearInterval(id); }; }, []); const overallOk = window.SERVERS.some((s) => statusMap[s.id]?.status === "ok"); return (
{/* Hero — server status pinned top-left of the page, banner centered */}
{/* Server status — anchored top-left, just under the header */}
{t.home.statusTitle}
{window.SERVERS.map((s, i) => { const live = statusMap[s.id]; const isOk = live?.status === "ok"; const isOff = live && live.status !== "ok"; const dotColor = isOk ? "var(--ok)" : isOff ? "var(--err, #b91c1c)" : "var(--text-3)"; const labelColor = dotColor; const label = isOk ? t.home.online : isOff ? t.home.offline : t.home.checking; const subLine = isOk && live.ping_ms != null ? `ping · ${live.ping_ms} ms` : (s.host ? s.host : ""); return (
{lang === "ru" ? s.regionRu : s.regionEn}
{subLine}
{label}
); })}
{/* Banner — centered, smaller */}

{t.hero.slogan}

{t.hero.sub}

} onClick={() => onGo("home", "download")}> {t.hero.download} } > {t.hero.github}
{/* What */}
{t.home.whatBody.split(/\n\n+/).map((para, i) => (

{para}

))}
{/* Features */}
{t.home.features.map((f, i) => { const Icon = [window.Ic.Lock, window.Ic.Network, window.Ic.Brace][i]; return (

{f.t}

{f.d}

); })}
{/* Status section removed from main flow — lives as overlay in the hero. */} {/* Tech stack */}
{/* Download */}
{Object.entries(t.home.tabs).map(([k, v]) => ( ))}
Filename
{art.file}
{t.home.sha256}
{art.sha ? :
{art.note || "—"}
}
} > {art.file.split(".").pop().toUpperCase()}
{open && (
)} {/* Cross-link to the self-host quick-start. Distinct from the per-platform client downloads above — Docker is the server side, lives in Servers. */}
{t.home.selfHostHint} { e.preventDefault(); onGo && onGo("servers"); }} style={{ color: "var(--accent, #5288C1)", textDecoration: "underline" }} > {t.home.selfHostLink}
); }; /* ---------- SECURITY ---------- */ const SecurityPage = ({ t }) => { const [open, setOpen] = useState(0); return (
{t.sec.crypto.map(([k, v], i) => (
{k}
{v}
))}

{t.sec.noSeeTitle}

    {t.sec.noSee.map((x, i) => (
  • {x}
  • ))}

{t.sec.seeTitle}

    {t.sec.see.map((x, i) => (
  • {x}
  • ))}
{t.sec.defendTitle}
    {t.sec.defend.map((x, i) => (
  • {x}
  • ))}
{t.sec.noDefendTitle}
    {t.sec.noDefend.map((x, i) => (
  • {x}
  • ))}
{t.sec.known.map((k, i) => { const isOpen = open === i; return ( {isOpen && (

{k.d}

Workaround

{k.w}

)}
); })}
); }; /* ---------- SERVERS ---------- */ const ServersPage = ({ t }) => { return (
{t.srv.warn}
    {t.srv.pick.map((x, i) => (
  1. {String(i + 1).padStart(2, "0")} {x}
  2. ))}

{t.srv.storedTitle}

    {t.srv.stored.map((x, i) => (
  • · {x}
  • ))}

{t.srv.offTitle}

{t.srv.offBody}

{t.srv.fallbackTitle}

{t.srv.fallback}

{/* Requirements — not a numbered step, just a prerequisite list. */} {t.srv.qsRequirements && (

{t.srv.qsRequirementsTitle}

    {t.srv.qsRequirements.map((r, i) => (
  • · {r}
  • ))}
)} {/* Numbered steps — install + run + verify. */}
    {t.srv.steps.map((s, i) => (
  1. {t.srv.step}
    {String(i + 1).padStart(2, "0")}

    {s.t}

    {s.d}

    {s.code && ( )}
  2. ))}
{/* "Next" — operational topics that aren't part of the launch sequence. */} {t.srv.qsAfter && (

{t.srv.qsAfterTitle}

{t.srv.qsAfter.map((it, i) => (

{it.t}

{it.d}

{it.code && ( )}
))}
)}
); }; /* ---------- DOCS ---------- */ const DocsPage = ({ t }) => (
github.com/shchuchkin-pkims/fear/wiki

{t.doc.intro}

} > {t.doc.goto}
{t.doc.sections.map((s, i) => (

{s.t}

{s.d}

→ {t.doc.readWiki}
))}
); window.HomePage = HomePage; window.SecurityPage = SecurityPage; window.ServersPage = ServersPage; window.DocsPage = DocsPage;