// Main app for eNFence single-page redesign. // Sections: Nav, Hero, Capability strip, Services, Why, Stack, Contact, Footer. const { useState, useEffect, useRef, useMemo } = React; // ─── Content (DE + EN) ────────────────────────────────────────────────────── const CONTENT = { de: { nav: { management: "Services", about: "Über uns", contact: "Kontakt", cta: "Beratung anfragen" }, hero: { status: "Aktives Monitoring · 24/7", eyebrow: "IBM Business Partner · seit 2013", h1a: "Wir betreuen Ihre", h1b: "Power.", sub: "Wir sind die Spezialisten für IBM Power. Wir planen, betreiben und optimieren AIX- und Linux-Landschaften für Unternehmen, die auf Verlässlichkeit angewiesen sind.", cta1: "Services", cta2: "Gespräch vereinbaren", }, stripTitle: "Wofür wir stehen", strip: [ { k: "100%", v: "Automation" }, { k: "24/7", v: "Betrieb" }, { techs: ["AIX", "IBM i", "Linux", "PowerVM", "HMC", "Ansible", "Terraform"] }, ], servicesEyebrow: "Leistungen", servicesTitle: "Sechs Disziplinen, eine Verantwortung.", servicesIntro: "Vom täglichen Betrieb bis zur strategischen Migration — wir decken den gesamten Lebenszyklus Ihrer Power-Landschaft ab.", services: [ { n: "01", t: "System Management", d: "Wir stellen den reibungslosen Betrieb Ihrer IBM Power Systemlandschaft sicher — von der permanenten Überwachung bis zur Fehleranalyse und Beseitigung.", tags: ["Monitoring", "Incident", "Patching"], }, { n: "02", t: "Planung", d: "Anhand Ihrer Anforderungen planen und gestalten wir die Neu- und Weiterentwicklung Ihrer IBM Power Server — Funktionalitäten, Geschäftsprozesse und Schnittstellen inklusive.", tags: ["Architektur", "Roadmap", "Kapazität"], }, { n: "03", t: "Virtualisierung", d: "Virtuelle Betriebsumgebungen, um schnell auf neue Systemanforderungen zu reagieren — mit dem Nebeneffekt, dass ungenutzte Hardware-Ressourcen gezielt eingesetzt werden.", tags: ["PowerVM", "Live Partition Mobility", "VIOS"], }, { n: "04", t: "Migration", d: "Eine Migration von AIX auf Linux erfordert nicht nur gute Planung, sondern auch eine durchdachte Umsetzung anhand fundiertem Know-How in beiden Systemen.", tags: ["AIX→Linux", "P9→P11", "Zero-Downtime"], }, { n: "05", t: "Security", d: "Sicherheit ist heute wichtiger als je zuvor — im Bezug auf Gefahren von außen und innen ebenso wie auf Risiken durch Ausfälle und Fehlkonfigurationen.", tags: ["Hardening", "Audit", "Compliance"], }, { n: "06", t: "Health Check", d: "Detaillierte Berichte und Analysen geben Aufschluss über Risikobereiche und Schwachstellen innerhalb der IT-Landschaft — und werden gezielt abgestellt.", tags: ["Assessment", "Reporting", "Performance"], }, ], whyEyebrow: "Warum eNFence", whyTitle: "Kompetenz und Fachwissen aus einer Hand.", whyP1: "Die rasante Entwicklung in der globalen IT schreitet unaufhaltsam voran und bringt immer neue Herausforderungen mit sich. Was heute ein leistungsstarkes System ist, kann morgen überholt sein.", whyP2: "Für Unternehmen, deren Betriebsprozesse auf großen Datenmengen, komplexen Netzwerken und umfangreichen Systemlandschaften beruhen, bietet der Wandel viele Möglichkeiten — birgt aber auch Risiken.", whyBullets: [ "Ein Ansprechpartner für den gesamten Power-Stack", "Planung, Betrieb und Pflege aus einer Hand", "Vorausschauend für individuelle Strategien optimiert", "Schulung und Wissensaufbau im Team inklusive", ], stackEyebrow: "Was wir betreiben", stackTitle: "Der Power-Stack, durchgehend betreut.", stackLayers: [ { lvl: "L4", label: "Anwendungen", items: ["SAP", "Oracle", "Db2", "PostgreSQL", "Custom Workloads"] }, { lvl: "L3", label: "Betriebssysteme", items: ["AIX", "IBM i", "Red Hat", "SUSE", "Ubuntu"] }, { lvl: "L2", label: "Virtualisierung", items: ["PowerVM", "VIOS", "Live Partition Mobility", "HMC"] }, { lvl: "L1", label: "Hardware", items: ["Power11", "Power10", "Power9", "IBM FlashSystems"] }, ], contactEyebrow: "Kontakt", contactTitle: "Sprechen wir über Ihre Power-Landschaft.", contactSub: "Nutzen Sie das Formular oder rufen Sie uns direkt an. Wir antworten in der Regel innerhalb eines Werktages.", form: { name: "Name", phone: "Telefon", email: "E-Mail", msg: "Nachricht", consent: "Ich bin mit der Datenschutzerklärung einverstanden.", send: "Nachricht senden", sending: "Wird gesendet …", sent: "Danke — wir melden uns.", errGeneric: "Senden fehlgeschlagen. Bitte versuchen Sie es erneut oder schreiben Sie uns direkt.", errRate: "Zu viele Anfragen — bitte versuchen Sie es später erneut.", errEmail: "Bitte geben Sie eine gültige E-Mail-Adresse an.", errName: "Bitte geben Sie Ihren Namen an.", errMsg: "Bitte schreiben Sie uns eine kurze Nachricht.", }, officeLabel: "Büro", phoneLabel: "Telefon", mailLabel: "E-Mail", address: ["Wiesenstraße 5", "65843 Sulzbach (Taunus)", "Deutschland"], phone: "+49 6196 88 404 55", mail: "info@enfence.com", footerLine: "WE handle your Power.", }, en: { nav: { management: "Services", about: "About", contact: "Contact", cta: "Book a call" }, hero: { status: "Active monitoring · 24/7", eyebrow: "IBM Business Partner · since 2013", h1a: "We handle your", h1b: "Power.", sub: "We are the specialists for IBM Power. We plan, run and optimise AIX and Linux landscapes for organisations that depend on reliability.", cta1: "Services", cta2: "Schedule a call", }, stripTitle: "What we stand for", strip: [ { k: "100%", v: "automation" }, { k: "24/7", v: "operations" }, { techs: ["AIX", "IBM i", "Linux", "PowerVM", "HMC", "Ansible", "Terraform"] }, ], servicesEyebrow: "Services", servicesTitle: "Six disciplines, one accountability.", servicesIntro: "From day-to-day operations to strategic migrations — we cover the full life-cycle of your Power landscape.", services: [ { n: "01", t: "System Management", d: "We keep your IBM Power landscape running smoothly — from continuous monitoring to root-cause analysis and remediation.", tags: ["Monitoring", "Incident", "Patching"], }, { n: "02", t: "Planning", d: "We design the next iteration of your IBM Power servers against your real requirements — features, business processes and the interfaces in between.", tags: ["Architecture", "Roadmap", "Capacity"], }, { n: "03", t: "Virtualisation", d: "Virtual environments let you respond fast to changing requirements — and put dormant hardware capacity to work in the process.", tags: ["PowerVM", "Live Partition Mobility", "VIOS"], }, { n: "04", t: "Migration", d: "An AIX-to-Linux migration calls for more than a good plan — it calls for executed know-how on both sides of the bridge.", tags: ["AIX→Linux", "P9→P11", "Zero-downtime"], }, { n: "05", t: "Security", d: "System security matters more than ever — against outside threats, inside risk, and the misconfigurations that cause unplanned outages.", tags: ["Hardening", "Audit", "Compliance"], }, { n: "06", t: "Health Check", d: "Detailed reports surface risk areas and weak spots in your landscape — so you can fix the right things, in the right order.", tags: ["Assessment", "Reporting", "Performance"], }, ], whyEyebrow: "Why eNFence", whyTitle: "Competence and expertise, from one team.", whyP1: "Global IT moves fast and surfaces new challenges with each cycle. A system that looks powerful today can be outdated tomorrow.", whyP2: "For organisations whose operations rest on large data sets, complex networks and broad system landscapes, that change is opportunity — and risk in equal measure.", whyBullets: [ "One partner for the entire Power stack", "Plan, run and maintain — under one roof", "Tuned forward to your individual strategy", "Knowledge transfer to your team along the way", ], stackEyebrow: "What we run", stackTitle: "The Power stack, end to end.", stackLayers: [ { lvl: "L4", label: "Applications", items: ["SAP", "Oracle", "Db2", "PostgreSQL", "Custom workloads"] }, { lvl: "L3", label: "Operating systems", items: ["AIX", "IBM i", "Red Hat", "SUSE", "Ubuntu"] }, { lvl: "L2", label: "Virtualisation", items: ["PowerVM", "VIOS", "Live Partition Mobility", "HMC"] }, { lvl: "L1", label: "Hardware", items: ["Power11", "Power10", "Power9", "IBM FlashSystems"] }, ], contactEyebrow: "Contact", contactTitle: "Let's talk about your Power landscape.", contactSub: "Use the form below or call us directly. We usually reply within one working day.", form: { name: "Name", phone: "Phone", email: "Email", msg: "Message", consent: "I agree to the privacy policy.", send: "Send message", sending: "Sending …", sent: "Thanks — we'll be in touch.", errGeneric: "Sending failed. Please try again or email us directly.", errRate: "Too many requests — please try again later.", errEmail: "Please enter a valid email address.", errName: "Please enter your name.", errMsg: "Please add a short message.", }, officeLabel: "Office", phoneLabel: "Phone", mailLabel: "Email", address: ["Wiesenstraße 5", "65843 Sulzbach (Taunus)", "Germany"], phone: "+49 6196 88 404 55", mail: "info@enfence.com", footerLine: "WE handle your Power.", }, }; // ─── Helpers ──────────────────────────────────────────────────────────────── function Mono({ children, className = "", style }) { return ( {children} ); } // ─── Nav ──────────────────────────────────────────────────────────────────── function Nav({ c, lang, setLang }) { const [scrolled, setScrolled] = useState(false); useEffect(() => { const onScroll = () => setScrolled(window.scrollY > 12); window.addEventListener("scroll", onScroll, { passive: true }); onScroll(); return () => window.removeEventListener("scroll", onScroll); }, []); return ( ); } // ─── Hero ─────────────────────────────────────────────────────────────────── function Hero({ c }) { return (
— {c.hero.eyebrow}

{c.hero.h1a}
{c.hero.h1b}

{c.hero.sub}

{c.hero.cta1} {c.hero.cta2}
); } const LPARS = [ { id: "lpar-prod-01", model: "9080-HEX*SN123456", kind: "prod", status: "healthy", cpu: 28, mem: 64, io: 142 }, { id: "lpar-prod-02", model: "9080-HEU*SN654321", kind: "prod", status: "healthy", cpu: 51, mem: 78, io: 210 }, { id: "lpar-test-01", model: "9080-HEX*SN123456", kind: "test", status: "idle", cpu: 12, mem: 41, io: 88 }, ]; const EVENTS = [ { kind: "ok", lpar: "prod-01", msg: "vios-1 heartbeat received", ts: "14:32:18" }, { kind: "info", lpar: "prod-02", msg: "errpt scan complete — 0 new", ts: "14:31:55" }, { kind: "ok", lpar: "prod-01", msg: "lpm dryrun → prod-02 passed", ts: "14:31:02" }, { kind: "warn", lpar: "test-01", msg: "hmc certificate expires in 41d", ts: "14:30:44" }, { kind: "ok", lpar: "prod-02", msg: "patch 7300-02-01 applied", ts: "14:29:17" }, { kind: "info", lpar: "test-01", msg: "snapshot created — 240GB", ts: "14:28:39" }, { kind: "ok", lpar: "prod-01", msg: "nightly backup completed — 1.8TB", ts: "14:27:05" }, ]; const ROW_H = 22; // px — must match .log min-height in CSS const WINDOW = 4; // visible events at once const TICK_MS = 1000; // advance one event per second const SLIDE_MS = 460; // slide animation duration function clamp(n) { return Math.max(0, Math.min(100, Math.round(n))); } function StatusBoard({ c }) { const [active, setActive] = useState(0); // selected LPAR const [offset, setOffset] = useState(0); // top event index in the cycle const [animating, setAnimating] = useState(false); const [t, setT] = useState(0); // metrics jitter tick // metric jitter useEffect(() => { const i = setInterval(() => setT((x) => x + 1), 1500); return () => clearInterval(i); }, []); // event scroll — advance one row per TICK_MS useEffect(() => { let snap; const tick = setInterval(() => { setAnimating(true); snap = setTimeout(() => { setAnimating(false); setOffset((o) => (o + 1) % EVENTS.length); }, SLIDE_MS); }, TICK_MS + SLIDE_MS); return () => { clearInterval(tick); clearTimeout(snap); }; }, []); const lpar = LPARS[active]; const cpu = clamp(lpar.cpu + ((t * 5) % 11) - 5); const mem = clamp(lpar.mem + ((t * 3) % 7) - 3); const io = Math.max(0, Math.round(lpar.io + ((t * 13) % 41) - 20)); const nowTs = new Date().toISOString().slice(11, 19); // render WINDOW+1 rows so we can translate the topmost out const rows = Array.from({ length: WINDOW + 1 }, (_, i) => EVENTS[(offset + i) % EVENTS.length]); const statusColor = lpar.status === "healthy" ? "ok" : "info"; return (
{lpar.model} / {lpar.id}
{lpar.status}
{LPARS.map((l, i) => ( ))}
CPU
{cpu}%
MEM
{mem}%
IO
{io}MB/s
{rows.map((e, i) => ( ))}
3 LPARs · 2 frames last sync {nowTs} UTC
); } function LogLine({ ts, kind, lpar, msg }) { return (
{ts} {kind.toUpperCase()} {lpar} {msg}
); } // ─── Strip (capability quad) ──────────────────────────────────────────────── function Strip({ c }) { return (
[ {c.stripTitle} ]
    {c.strip.map((it, i) => (
  • {it.techs ? (
      {it.techs.map((tech) => (
    • {tech}
    • ))}
    ) : (
    {it.k}
    {it.v}
    )}
  • ))}
); } // ─── Services grid ────────────────────────────────────────────────────────── function Services({ c }) { return (
{c.services.map((s) => (
{s.n}

{s.t}

{s.d}

    {s.tags.map((tg) => (
  • {tg}
  • ))}
))}
); } function SectionHead({ eyebrow, title, intro }) { return (
— {eyebrow}

{title}

{intro ?

{intro}

: null}
); } // ─── Why us ───────────────────────────────────────────────────────────────── function Why({ c }) { return (
— {c.whyEyebrow}

{c.whyTitle}

{c.whyP1}

{c.whyP2}

    {c.whyBullets.map((b, i) => (
  • 0{i + 1} {b}
  • ))}
); } // ─── Stack diagram ────────────────────────────────────────────────────────── function Stack({ c }) { return (
{c.stackLayers.map((l) => (
{l.lvl}
{l.label}
    {l.items.map((it) => (
  • {it}
  • ))}
))}
); } // ─── Contact ──────────────────────────────────────────────────────────────── function Contact({ c }) { const [form, setForm] = useState({ name: "", phone: "", email: "", msg: "", consent: false, website: "" }); // 'idle' | 'sending' | 'sent' | 'error' const [status, setStatus] = useState("idle"); const [errMsg, setErrMsg] = useState(""); // Page-load timestamp — server uses this to filter out instant-submit bots. const t0Ref = useRef(Date.now()); const onSubmit = async (e) => { e.preventDefault(); if (!form.consent || status === "sending") return; // Lightweight client-side validation — server validates too. if (!form.name.trim()) { setStatus("error"); setErrMsg(c.form.errName); return; } if (!/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(form.email)) { setStatus("error"); setErrMsg(c.form.errEmail); return; } if (form.msg.trim().length < 5) { setStatus("error"); setErrMsg(c.form.errMsg); return; } setStatus("sending"); setErrMsg(""); try { const res = await fetch("/api/contact", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: form.name, email: form.email, phone: form.phone, msg: form.msg, consent: form.consent, website: form.website, // honeypot — must stay empty t0: t0Ref.current, }), }); const data = await res.json().catch(() => ({})); if (res.ok && data.ok) { setStatus("sent"); setForm({ name: "", phone: "", email: "", msg: "", consent: false, website: "" }); t0Ref.current = Date.now(); } else if (res.status === 429) { setStatus("error"); setErrMsg(c.form.errRate); } else if (data.error === "email_invalid") { setStatus("error"); setErrMsg(c.form.errEmail); } else if (data.error === "name_required") { setStatus("error"); setErrMsg(c.form.errName); } else if (data.error === "msg_required") { setStatus("error"); setErrMsg(c.form.errMsg); } else { setStatus("error"); setErrMsg(c.form.errGeneric); } } catch (_err) { setStatus("error"); setErrMsg(c.form.errGeneric); } }; const set = (k) => (e) => setForm((f) => ({ ...f, [k]: e.target.type === "checkbox" ? e.target.checked : e.target.value })); return (
— {c.contactEyebrow}

{c.contactTitle}

{c.contactSub}

{/* Honeypot — hidden from real users, bots will fill it. */} {status === "error" && (
{errMsg}
)} {status === "sent" && (
{c.form.sent}
)}
); } function Field({ label, v, onChange, type = "text", full }) { const id = "f-" + label.replace(/\W/g, "-").toLowerCase(); return (
{type === "textarea" ? (