/* ==========================================================================
LANDING: hero and category tiles.
========================================================================== */
const TILE_SPAN = {
stoffe: "wide", kontext: "wide",
sport: "", ernahrung: "", schlaf: "", gesundheit: "",
rezepte: "half", wettkampf: "half",
};
function CategoryTile({ cat, span }) {
const forumOnly = cat.forumOnly;
const href = forumOnly ? "/forum?kind=recipe" : "/wissen?cat=" + cat.id;
const count = forumOnly ? (cat.forumCount || 0) : cat.count;
return (
{cat.image && (
{ e.target.style.display = "none"; }}
/>
)}
→
{BB.num(count)} {BB.t("Einträge")}
{cat.readCount > 0 && · {BB.num(cat.readCount)} {BB.t("gelesen")} }
{cat.label}
{cat.blurb &&
{cat.blurb}
}
);
}
function KnowledgeLanding({ categories, stats }) {
const tops = useMemo(
() => BB.children(categories, null).filter((c) => TILE_SPAN[c.id] !== undefined),
[categories]
);
const rest = useMemo(
() => BB.children(categories, null).filter((c) => TILE_SPAN[c.id] === undefined),
[categories]
);
return (
<>
{/* ---------------- hero ---------------- */}
{BB.t("Wissensbasis & Community")}
{words(BB.t("Alles über den Sport,"))}
{BB.t("ehrlich")} {" "}
{words(BB.t("dokumentiert."))}
{BB.t("{n} kuratierte Einträge zu Pharmakologie, Physiologie, Training und Ernährung, plus ein Forum, in dem Mitglieder ihre Artikel, Storys, Stacks, Rezepte und Transformationen teilen.")
.replace("{n}", BB.num(stats.wiki))}
{BB.t("Wissensbasis öffnen")}
{BB.t("Ins Forum")}
{BB.t("Community-Beiträge")}
{/* ---------------- categories ---------------- */}
{BB.t("Hauptkategorien")}
{BB.t("Wo willst du anfangen?")}
{BB.t("Jede Kategorie ist ein eigener Baum aus Unterthemen, von der Molekülstruktur bis zum fertigen Trainingsplan.")}
{tops.map((c) => )}
{rest.length > 0 && (
{rest.map((c) => (
{c.label} {c.count}
))}
)}
>
);
}
/* ==========================================================================
STARTSEITE: Premium-Funktionen zuerst. Die Wissensbasis hat ihre eigene
Landingpage in KnowledgeLanding; hier wird das Produkt als zusammenhaengendes
System aus Analyse, Wissen und Molekuelkarte gezeigt.
========================================================================== */
const HOME_ANALYSES = [
{
id: "split", number: "01", label: "Trainingssplit",
title: "Volumen sehen. Erholung verstehen.",
copy: "Sätze, Frequenz und Übungsauswahl werden zu einer lesbaren Woche. Du siehst nicht nur den Score, sondern jede Annahme darunter.",
score: 86, scoreLabel: "stimmige Belastung", note: "2 Stellen verdienen Aufmerksamkeit",
path: "M4 72 C22 68 30 30 49 37 S78 78 98 52 S128 17 151 28 S180 65 206 20",
metrics: [["Wochenvolumen", 0.86], ["Erholung", 0.74], ["Abdeckung", 0.92]],
facts: ["18 Muskelgruppen", "5 Trainingstage", "jede Zahl begründet"],
},
{
id: "stack", number: "02", label: "Stack",
title: "Zusammenspiel statt Einzelliste.",
copy: "Wirkstoffe, Dosis und Dauer werden gemeinsam betrachtet: Zielabdeckung, Redundanz, Organlast und bekannte Interaktionen bleiben getrennt nachvollziehbar.",
score: 62, scoreLabel: "erhöhte Komplexität", note: "3 Überschneidungen im Blick behalten",
path: "M4 64 C22 42 38 52 54 31 S82 20 101 48 S130 76 148 43 S183 28 206 39",
metrics: [["Zielabdeckung", 0.81], ["Redundanz", 0.58], ["Datenlage", 0.69]],
facts: ["5 Wirkstoffe", "11 Wirkungspfade", "keine Empfehlung"],
},
{
id: "meal", number: "03", label: "Mahlzeit",
title: "Mehr Kontext als Makros.",
copy: "Eine Mahlzeit wird für ihren Anlass gelesen. Nährwerte, Kaloriendichte, Verdauung und Mikronährstoffe landen in derselben verständlichen Ansicht.",
score: 91, scoreLabel: "passt zum Trainingsziel", note: "Proteinverteilung ist der stärkste Punkt",
path: "M4 67 C24 63 34 48 51 51 S77 58 94 36 S119 18 140 31 S173 59 206 24",
metrics: [["Protein", 0.94], ["Energiedichte", 0.72], ["Mikronährstoffe", 0.79]],
facts: ["742 kcal", "54 g Protein", "Anlass einbezogen"],
},
{
id: "blood", number: "04", label: "Blutbild",
title: "Werte im richtigen Zusammenhang.",
copy: "Messwerte werden im Referenzbereich verortet und mit Training, Belastung und zeitlichem Verlauf gelesen. Auffällig heißt nicht automatisch schlecht.",
score: 78, scoreLabel: "überwiegend unauffällig", note: "2 Werte brauchen Verlauf statt Momentaufnahme",
path: "M4 55 C23 35 39 61 57 42 S88 30 107 37 S131 62 151 48 S178 17 206 30",
metrics: [["Referenzlage", 0.82], ["Verlauf", 0.67], ["Vollständigkeit", 0.88]],
facts: ["24 Messwerte", "3 Zeitpunkte", "Kontext statt Diagnose"],
},
];
const HOME_MAP_NODES = [
{ id: "testosteron", label: "Testosteron", name: "Testosteron", type: "substance", seed: true, tiefe: 0,
detail: "Ausgangspunkt der Demo. Von hier werden Bindungen, Umwandlungen und Analyse-Endpunkte sichtbar." },
{ id: "ar", label: "AR", name: "Androgenrezeptor", type: "receptor", tiefe: 1,
detail: "Der Androgenrezeptor verbindet den Stoff mit mehreren nachgelagerten biologischen Signalen." },
{ id: "cyp19a1", label: "CYP19A1", name: "Aromatase · CYP19A1", type: "enzyme", tiefe: 1,
detail: "Das Enzym bildet die Brücke zur Umwandlung von Testosteron in Estradiol." },
{ id: "estradiol", label: "Estradiol", name: "Estradiol", type: "metabolite", tiefe: 2,
detail: "Ein Metabolit mit eigenen Rezeptorwegen – nicht bloß ein Nebenprodukt in der Karte." },
{ id: "esr1", label: "ESR1", name: "Estrogenrezeptor alpha", type: "receptor", tiefe: 2,
detail: "Ein Zielprotein, über das Estradiol weitere regulatorische Wege erreicht." },
{ id: "protein", label: "MPS", name: "Muskelproteinsynthese", type: "endpoint", tiefe: 2,
detail: "Ein virtueller Analyse-Endpunkt bündelt mehrere belegte Wege, ohne sie zu einer Behauptung zu verkürzen." },
{ id: "shbg", label: "SHBG", name: "Sexualhormon-bindendes Globulin", type: "transporter", tiefe: 1,
detail: "Der Transportknoten zeigt, dass freie und gebundene Anteile im System nicht dasselbe sind." },
];
const HOME_MAP_EDGES = [
{ id: 1, src: "testosteron", dst: "ar", mechanism: "agonist", evidence: "measured", strength: 0.95, confidence: 0.94, messungen: 12 },
{ id: 2, src: "testosteron", dst: "cyp19a1", mechanism: "substrate", evidence: "measured", strength: 0.76, confidence: 0.89, messungen: 7 },
{ id: 3, src: "cyp19a1", dst: "estradiol", mechanism: "produces", evidence: "measured", strength: 0.86, confidence: 0.91, messungen: 8 },
{ id: 4, src: "estradiol", dst: "esr1", mechanism: "agonist", evidence: "measured", strength: 0.88, confidence: 0.93, messungen: 10 },
{ id: 5, src: "ar", dst: "protein", mechanism: "positive_modulator", evidence: "predicted", strength: 0.66, confidence: 0.72, effect: 1 },
{ id: 6, src: "testosteron", dst: "shbg", mechanism: "binding", evidence: "measured", strength: 0.72, confidence: 0.87, messungen: 5 },
];
const HOME_MAP_TYPE_LABEL = {
substance: "Substanz", metabolite: "Metabolit", receptor: "Rezeptor",
enzyme: "Enzym", transporter: "Transporter", regulator: "Regulator",
endpoint: "Analyse-Endpunkt",
};
function HomeHero({ stats }) {
return (
{words(BB.t("Bodybuilding."))}
{words(BB.t("Bis ins Molekül."))}
{BB.t("Analysen, kuratiertes Wissen und biologische Zusammenhänge – transparent gerechnet, sauber belegt und an einem Ort.")}
{BB.num(stats.wiki || 0)} {BB.t("Wissenseinträge")}
4 {BB.t("Analysemodelle")}
1 {BB.t("verknüpftes System")}
);
}
function HomeAnalysisPreview({ story }) {
return (
{BB.t("Beispielanalyse")}
{BB.t("vollständig begründet")}
{BB.t("Gesamtbild")}
{story.score}
{BB.t(story.scoreLabel)}
{story.metrics.map(([label, value]) => (
{BB.t(label)} {Math.round(value * 100)}%
))}
↳ {BB.t(story.note)}
);
}
function HomeAnalysisShowcase() {
const [active, setActive] = useState(0);
const [direction, setDirection] = useState(1);
const stageRef = useRef(null);
const animationRef = useRef(null);
const story = HOME_ANALYSES[active];
useEffect(() => {
const stage = stageRef.current;
if (!stage || BB.reducedMotion() || !stage.animate) return undefined;
if (animationRef.current) animationRef.current.cancel();
animationRef.current = stage.animate(
[
{ opacity: 0.35, transform: `translate3d(${direction * 24}px, 0, 0)`, filter: "blur(2px)" },
{ opacity: 1, transform: "translate3d(0, 0, 0)", filter: "blur(0)" },
],
{ duration: 600, easing: "cubic-bezier(0.23, 1, 0.32, 1)" }
);
return () => { if (animationRef.current) animationRef.current.cancel(); };
}, [active, direction]);
function select(next) {
if (next === active) return;
setDirection(next > active ? 1 : -1);
setActive(next);
}
function step(delta) {
const next = (active + delta + HOME_ANALYSES.length) % HOME_ANALYSES.length;
setDirection(delta > 0 ? 1 : -1);
setActive(next);
}
return (
{BB.t("Premium Analysen")}
{BB.t("Nicht nur ein Score.")} {BB.t("Die ganze Rechnung.")}
{BB.t("Jede Auswertung zeigt, wie sie zu ihrem Ergebnis kommt – vom Eingangswert bis zur Quelle. Vier Perspektiven, dieselbe Klarheit.")}
{
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
event.preventDefault();
const delta = event.key === "ArrowRight" ? 1 : -1;
const next = (active + delta + HOME_ANALYSES.length) % HOME_ANALYSES.length;
select(next);
const tabs = event.currentTarget.querySelectorAll('[role="tab"]');
if (tabs[next]) tabs[next].focus();
}}
>
{HOME_ANALYSES.map((item, index) => (
select(index)}
>
{item.number}
{BB.t(item.label)}
↗
))}
{
if (event.key === "ArrowLeft") { event.preventDefault(); step(-1); }
if (event.key === "ArrowRight") { event.preventDefault(); step(1); }
}}
>
{story.number} / 04 · {BB.t(story.label)}
{BB.t(story.title)}
{BB.t(story.copy)}
{story.facts.map((fact) => ✓ {BB.t(fact)} )}
{BB.t("Analyse öffnen")}
↗
0{active + 1} 04
step(-1)} aria-label={BB.t("Vorherige Analyse")}>←
step(1)} aria-label={BB.t("Nächste Analyse")}>→
);
}
function HomeMoleculeDemo() {
const [selected, setSelected] = useState(HOME_MAP_NODES[0]);
function pick(node) {
const match = HOME_MAP_NODES.find((item) => String(item.id) === String(node.id));
if (match) setSelected(match);
}
const links = HOME_MAP_EDGES.filter((edge) => edge.src === selected.id || edge.dst === selected.id).length;
return (
{BB.t("Molekülkarte")}
{BB.t("Vom Stoff zum System.")} {BB.t("Eine Verbindung nach der anderen.")}
{BB.t("Diese kleine Auswahl nutzt bereits die echte Kartenansicht. Wähle eine Node und verfolge, wie Substanz, Enzym, Rezeptor und Endpunkt zusammenhängen.")}
{BB.t("Interaktive Demo")}
{HOME_MAP_NODES.length} {BB.t("ausgewählte Nodes")}
{}} onZuklappen={() => {}} onNodeSelect={pick}
/>
{BB.t(HOME_MAP_TYPE_LABEL[selected.type] || selected.type)}
{BB.t("Ausgewählte Node")}
{BB.t(selected.name)}
{BB.t(selected.detail)}
{BB.t("Verbindungen in der Demo")} {links}
{BB.t("Datenstatus")} {BB.t("gemessen + modelliert")}
{BB.t("Substanz")}
{BB.t("Rezeptor")}
{BB.t("Enzym")}
{BB.t("Endpunkt")}
{BB.t("Node auswählen")}
{HOME_MAP_NODES.map((node) => (
setSelected(node)}
>
{node.label}
))}
{BB.t("Ganze Karte erkunden")} ↗
);
}
function HomeClosing() {
return (
{BB.t("Bodybuilding Bible Premium")}
{BB.t("Deine Frage ist der Anfang.")} {BB.t("Nicht das Ende der Erklärung.")}
{BB.t("Starte mit einer Analyse oder tauche kostenlos in die kuratierte Wissensbasis ein.")}
{BB.t("Analyse starten")}
{BB.t("Wissen entdecken")}
);
}
function HomeLoader({ ready }) {
return (
);
}
function Landing({ categories, stats }) {
const [ready, setReady] = useState(() => {
try { return window.sessionStorage.getItem("bb-home-image-ready") === "1"; }
catch (_) { return false; }
});
useEffect(() => {
if (ready) return undefined;
const previousOverflow = document.body.style.overflow;
const startedAt = performance.now();
const image = new Image();
let active = true;
let settled = false;
let revealTimer;
let fallbackTimer;
document.body.style.overflow = "hidden";
document.body.classList.add("home-is-loading");
const reveal = () => {
if (settled) return;
settled = true;
window.clearTimeout(fallbackTimer);
const remaining = Math.max(0, 400 - (performance.now() - startedAt));
revealTimer = window.setTimeout(() => {
if (!active) return;
try { window.sessionStorage.setItem("bb-home-image-ready", "1"); }
catch (_) { /* Storage can be disabled without blocking the page. */ }
setReady(true);
}, remaining);
};
image.onload = reveal;
image.onerror = reveal;
image.src = "/static/assets/landing-gym.jpg?v=hero4k1";
if (image.decode) image.decode().then(reveal).catch(() => { if (image.complete) reveal(); });
fallbackTimer = window.setTimeout(reveal, 4000);
return () => {
active = false;
window.clearTimeout(revealTimer);
window.clearTimeout(fallbackTimer);
image.onload = null;
image.onerror = null;
document.body.style.overflow = previousOverflow;
document.body.classList.remove("home-is-loading");
};
}, [ready]);
return (
);
}
Object.assign(window, { Landing, KnowledgeLanding, CategoryTile });