Files

433 lines
15 KiB
JavaScript

/* ToolCTRL — Landing Page Interaktion */
(function () {
"use strict";
/* ---- Jahr im Footer ---- */
var yearEl = document.getElementById("year");
if (yearEl) yearEl.textContent = String(new Date().getFullYear());
/* ---- Mobile-Navigation ---- */
var toggle = document.querySelector(".nav-toggle");
var mobileNav = document.querySelector(".mobile-nav");
var header = document.querySelector(".site-header");
function updateHeaderState() {
if (!header) return;
header.classList.toggle("is-scrolled", window.scrollY > 12);
}
updateHeaderState();
window.addEventListener("scroll", updateHeaderState, { passive: true });
if (toggle && mobileNav) {
toggle.addEventListener("click", function () {
var open = mobileNav.hasAttribute("hidden");
if (open) {
mobileNav.removeAttribute("hidden");
toggle.setAttribute("aria-expanded", "true");
} else {
mobileNav.setAttribute("hidden", "");
toggle.setAttribute("aria-expanded", "false");
}
});
mobileNav.querySelectorAll("a").forEach(function (a) {
a.addEventListener("click", function () {
mobileNav.setAttribute("hidden", "");
toggle.setAttribute("aria-expanded", "false");
});
});
}
/* ---- Console-Easter-Eggs fuer neugierige Inspektoren ---- */
var inspectAscii = [
" _______ _______ _______ ___ _______ _______ _______ ",
"| || || || | | || || |",
"|_ _|| _ || _ || | | ||_ _|| ___|",
" | | | | | || | | || | | | | | | |___ ",
" | | | |_| || |_| || |___ | _| | | | ___|",
" | | | || || || |_ | | | |___ ",
" |___| |_______||_______||_______||_______| |___| |_______|"
].join("\n");
var inspectHints = [
"CTRL-KEY EVENT: Inspect gedrueckt. Bitte keine Produktivsysteme anfassen.",
"Hidden combo: window.toolctrl.press()",
"Status: schwarz/weiss. Farbe wurde zurueck in den Werkzeugkoffer gelegt."
];
var inspectLogged = false;
function logControlConsole(reason) {
if (reason === "devtools" && inspectLogged) return;
if (reason === "devtools") inspectLogged = true;
console.log(
"%c" + inspectAscii,
"font-family: monospace; font-weight: 900; color: #000; background: #fff; padding: 10px; border: 3px solid #000;"
);
console.log("%c" + inspectHints[Math.floor(Math.random() * inspectHints.length)],
"font-family: monospace; color: #fff; background: #000; padding: 6px 8px;");
}
window.toolctrl = window.toolctrl || {};
window.toolctrl.press = function () {
console.log("%c[ CTRL ] *klack* Ablauf uebernommen.", "font-family: monospace; font-weight: 900;");
return "pressed";
};
window.addEventListener("load", function () {
setTimeout(function () { logControlConsole("load"); }, 450);
});
setInterval(function () {
var open = Math.abs(window.outerWidth - window.innerWidth) > 160 ||
Math.abs(window.outerHeight - window.innerHeight) > 160;
document.documentElement.toggleAttribute("data-inspecting", open);
if (open) logControlConsole("devtools");
}, 1200);
/* ---- Dezente Scroll-Reveals ---- */
var reduceMotion = window.matchMedia &&
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
var revealEls = Array.prototype.slice.call(document.querySelectorAll([
".hero-copy > *",
".hero-visual",
".trust-strip span",
".stance-head > *",
".stance-card",
".product-copy > *",
".product-ui",
".velocity-copy > *",
".velocity-item",
".section-head",
".card",
".industry-card",
".step",
".arbeit-copy > *",
".kanban-wrap",
".event-poster",
".mini-terminal",
".event-card",
".kontakt-copy > *",
".contact-promises li",
".lead-form",
".td-hero .wrap > *",
".td-feature > *",
".td-talk",
".speaker-card",
".shot",
".gj-teaser"
].join(",")));
revealEls.forEach(function (el, index) {
el.classList.add("reveal", "reveal-delay-" + (index % 5));
});
if (reduceMotion || !("IntersectionObserver" in window)) {
revealEls.forEach(function (el) { el.classList.add("is-visible"); });
} else {
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) return;
entry.target.classList.add("is-visible");
observer.unobserve(entry.target);
});
}, { threshold: 0.12, rootMargin: "0px 0px -8% 0px" });
revealEls.forEach(function (el) { observer.observe(el); });
}
/* ---- Hero Workflow Nodes: ziehbare Kontrollpunkte ---- */
var board = document.querySelector("[data-control-board]");
if (board) {
var lineSvg = board.querySelector(".control-lines");
var logoNode = board.querySelector("[data-logo-node]");
var nodes = Array.prototype.slice.call(board.querySelectorAll("[data-node]"));
var draggableItems = nodes.concat(logoNode ? [logoNode] : []);
var activeDrag = null;
var lineFrame = 0;
if (logoNode) {
logoNode.addEventListener("dragstart", function (e) {
e.preventDefault();
});
}
function boardRect() {
return board.getBoundingClientRect();
}
function centerOf(el, b) {
var r = el.getBoundingClientRect();
return {
x: r.left - b.left + r.width / 2,
y: r.top - b.top + r.height / 2
};
}
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
function updateLines() {
if (!lineSvg || !logoNode) return;
var b = boardRect();
var target = centerOf(logoNode, b);
lineSvg.setAttribute("viewBox", "0 0 " + b.width + " " + b.height);
nodes.forEach(function (node) {
var path = lineSvg.querySelector('[data-line="' + node.dataset.node + '"]');
if (!path) return;
var nodeCenter = centerOf(node, b);
var isOutput = node.classList.contains("is-output");
var start = isOutput ? target : nodeCenter;
var end = isOutput ? nodeCenter : target;
var bend = Math.max(44, Math.abs(end.x - start.x) * 0.38);
var c1x = start.x + (end.x > start.x ? bend : -bend);
var c2x = end.x + (end.x > start.x ? -bend : bend);
var d = "M " + start.x + " " + start.y +
" C " + c1x + " " + start.y +
", " + c2x + " " + end.y +
", " + end.x + " " + end.y;
path.classList.toggle("is-output", isOutput);
path.setAttribute("d", d);
});
lineFrame = 0;
}
function requestLineUpdate() {
if (lineFrame) return;
lineFrame = window.requestAnimationFrame(updateLines);
}
draggableItems.forEach(function (item) {
item.addEventListener("pointerdown", function (e) {
var r = item.getBoundingClientRect();
if (item === logoNode) item.classList.add("is-positioned");
activeDrag = {
node: item,
isLogo: item === logoNode,
offsetX: e.clientX - r.left,
offsetY: e.clientY - r.top
};
item.classList.add("is-dragging");
item.setPointerCapture(e.pointerId);
e.preventDefault();
});
item.addEventListener("pointermove", function (e) {
if (!activeDrag || activeDrag.node !== item) return;
var b = boardRect();
var x = clamp(e.clientX - b.left - activeDrag.offsetX, 8, b.width - item.offsetWidth - 8);
var y = clamp(e.clientY - b.top - activeDrag.offsetY, 8, b.height - item.offsetHeight - 8);
item.style.left = x + "px";
item.style.top = y + "px";
if (activeDrag.isLogo) {
item.classList.add("is-positioned");
} else {
item.style.removeProperty("--x");
item.style.removeProperty("--y");
}
requestLineUpdate();
});
item.addEventListener("pointerup", function (e) {
if (!activeDrag || activeDrag.node !== item) return;
item.classList.remove("is-dragging");
item.releasePointerCapture(e.pointerId);
activeDrag = null;
requestLineUpdate();
});
item.addEventListener("pointercancel", function () {
item.classList.remove("is-dragging");
activeDrag = null;
});
});
window.addEventListener("resize", requestLineUpdate, { passive: true });
updateLines();
setTimeout(requestLineUpdate, 150);
}
/* ---- Produktwirkung: klickbare Workflow-Zustaende ---- */
var productUi = document.querySelector(".product-ui");
if (productUi) {
var productStates = {
eingang: {
kicker: "Heute · 14 aktive Vorgänge",
title: "Workflow Control",
pill: "3 Blocker",
flow: ["Anfrage", "Prüfung", "Angebot", "Termin", "Rechnung"],
active: 1,
done: 0,
metrics: [["Antwortzeit", "18 min"], ["Auto-Schritte", "74%"], ["Offene Übergaben", "03"]],
rows: [["Kunde wartet auf Rückmeldung", "Jetzt", "is-hot"], ["Dokumente aus Postfach übernommen", "Auto", ""], ["Team-Zuständigkeit gesetzt", "11:15", ""], ["Status an Leitung gemeldet", "Erledigt", "is-done"]]
},
angebote: {
kicker: "Vertrieb · 8 Angebote offen",
title: "Angebote steuern",
pill: "2 fällig",
flow: ["Lead", "Kalkulation", "Freigabe", "Versand", "Follow-up"],
active: 2,
done: 1,
metrics: [["Durchlauf", "2,4 h"], ["Vorlagen", "91%"], ["Rückfragen", "05"]],
rows: [["Angebot braucht interne Freigabe", "Heute", "is-hot"], ["PDF aus Kundendaten erstellt", "Auto", ""], ["Nachfass-Termin gesetzt", "Morgen", ""], ["Alte Version archiviert", "Erledigt", "is-done"]]
},
planung: {
kicker: "Team · 21 Einsätze geplant",
title: "Planung sichtbar",
pill: "1 Konflikt",
flow: ["Auftrag", "Ressource", "Slot", "Route", "Update"],
active: 2,
done: 1,
metrics: [["Auslastung", "82%"], ["Auto-Routen", "68%"], ["Konflikte", "01"]],
rows: [["Termin überschneidet sich", "Prüfen", "is-hot"], ["Route für Außendienst gebaut", "Auto", ""], ["Materialliste angehängt", "09:40", ""], ["Kunde informiert", "Erledigt", "is-done"]]
},
abschluss: {
kicker: "Abschluss · 36 Vorgänge fertig",
title: "Betrieb im Blick",
pill: "0 kritisch",
flow: ["Abnahme", "Doku", "Rechnung", "Feedback", "Archiv"],
active: 3,
done: 2,
metrics: [["Abnahmen", "96%"], ["Auto-Doku", "88%"], ["Offen", "00"]],
rows: [["Feedback für nächste Version notiert", "Neu", ""], ["Rechnung vorbereitet", "Auto", ""], ["Abnahme protokolliert", "13:20", "is-done"], ["Projekt sauber archiviert", "Erledigt", "is-done"]]
}
};
var productTabs = Array.prototype.slice.call(productUi.querySelectorAll("[data-product-tab]"));
var productKicker = productUi.querySelector("[data-product-kicker]");
var productTitle = productUi.querySelector("[data-product-title]");
var productPill = productUi.querySelector("[data-product-pill]");
var productFlow = productUi.querySelector("[data-product-flow]");
var productMetrics = productUi.querySelector("[data-product-metrics]");
var productTable = productUi.querySelector("[data-product-table]");
function setProductState(key) {
var state = productStates[key];
if (!state) return;
productUi.classList.add("is-switching");
productTabs.forEach(function (tab) {
tab.classList.toggle("active", tab.dataset.productTab === key);
});
window.setTimeout(function () {
if (productKicker) productKicker.textContent = state.kicker;
if (productTitle) productTitle.textContent = state.title;
if (productPill) productPill.textContent = state.pill;
if (productFlow) {
productFlow.innerHTML = state.flow.map(function (label, index) {
var cls = index === state.active ? "active" : (index <= state.done ? "done" : "");
return '<span class="' + cls + '">' + label + '</span>';
}).join("");
}
if (productMetrics) {
productMetrics.innerHTML = state.metrics.map(function (metric) {
return "<div><span>" + metric[0] + "</span><strong>" + metric[1] + "</strong></div>";
}).join("");
}
if (productTable) {
productTable.innerHTML = state.rows.map(function (row) {
return '<div class="ui-row ' + row[2] + '"><span>' + row[0] + '</span><b>' + row[1] + '</b></div>';
}).join("");
}
productUi.classList.remove("is-switching");
}, 90);
}
productTabs.forEach(function (tab) {
tab.addEventListener("click", function () {
setProductState(tab.dataset.productTab);
});
});
}
/* ---- Lead-Formular ---- */
var form = document.getElementById("lead-form");
var status = document.getElementById("form-status");
if (!form) return;
function setStatus(msg, kind) {
if (!status) return;
status.textContent = msg;
status.className = "form-status " + (kind || "");
status.hidden = false;
}
function fieldRow(input) {
return input.closest(".form-row");
}
function validate() {
var ok = true;
["f-name", "f-email", "f-nachricht"].forEach(function (id) {
var el = document.getElementById(id);
var row = fieldRow(el);
var valid = el.value.trim() !== "";
if (id === "f-email" && valid) {
valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(el.value.trim());
}
if (row) row.classList.toggle("invalid", !valid);
if (!valid) ok = false;
});
return ok;
}
form.addEventListener("submit", function (e) {
e.preventDefault();
if (!validate()) {
setStatus("Bitte füllen Sie die markierten Pflichtfelder aus.", "err");
return;
}
var btn = form.querySelector("button[type=submit]");
var payload = {
name: form.name.value.trim(),
firma: form.firma.value.trim(),
email: form.email.value.trim(),
telefon: form.telefon.value.trim(),
nachricht: form.nachricht.value.trim(),
website: form.website.value.trim(), // Honeypot
quelle: "Messe — Landing Page"
};
if (btn) { btn.disabled = true; btn.textContent = "Wird gesendet …"; }
setStatus("Anfrage wird übermittelt …", "");
fetch("/api/lead", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
})
.then(function (res) {
return res.json().then(function (data) {
return { ok: res.ok, data: data };
});
})
.then(function (r) {
if (r.ok) {
form.reset();
setStatus(
"Vielen Dank — Ihre Anfrage ist bei uns eingegangen. Wir melden uns zeitnah.",
"ok"
);
} else {
setStatus(
(r.data && r.data.error) ||
"Es ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.",
"err"
);
}
})
.catch(function () {
setStatus(
"Verbindung fehlgeschlagen. Bitte schreiben Sie uns direkt an info@tool-ctrl.de.",
"err"
);
})
.finally(function () {
if (btn) { btn.disabled = false; btn.textContent = "Anfrage senden"; }
});
});
/* Markierung beim Tippen entfernen */
form.querySelectorAll("input, textarea").forEach(function (el) {
el.addEventListener("input", function () {
var row = fieldRow(el);
if (row) row.classList.remove("invalid");
});
});
})();