Einfuehrung fuer Einsteiger: Folien mit echten Aufnahmen und Markierungen

Neue Seite "Einfuehrung" im Menue: 31 Folien in zwei Teilen - Bedienen im
Alltag (Grundriss, Raum, Rollos, Heizung, Licht, Zeitleiste, Solar, Auto,
Heizstab und Wasser, Statistik, Historie, Heizung, Wetter, Fahrzeug, Hilfe)
und Einrichten und Verwalten (Preise, Grundriss, Kacheln, Geraete,
Automatik anlegen in Wenn/Wann/Dann, Ketten, Fahrzeugschluessel,
Protokolle).

- Bilder sind echte Aufnahmen der Oberflaeche mit Livewerten, Handy- und
  Desktopgroesse. tools/einfuehrung/aufnehmen.ps1 steuert Edge ohne
  Fenster ueber das DevTools-Protokoll nach plan.json und schreibt zu jedem
  Bild die Lage der markierten Elemente; daraus sitzen die nummerierten
  Punkte. Wetterort und Schluesselendungen werden vor der Aufnahme
  abgedeckt.
- Blaettern per Knopf, Pfeiltaste oder Wischen; Kapitelsprung; die Folie
  steht in der Adresse (#folie-7). Erklaerung und Punkt heben sich
  gegenseitig hervor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 14:32:14 +02:00
co-authored by Claude Opus 5
parent 05029889b9
commit 865dd61535
51 changed files with 1297 additions and 1 deletions
+133
View File
@@ -0,0 +1,133 @@
/* ==========================================================================
Einführung als Folien (restricted/einfuehrung.php).
Blättern mit den Knöpfen, den Pfeiltasten oder durch Wischen. Die Folie
steht in der Adresse (#folie-7), damit man sie verlinken und nach einem
Neuladen dort weitermachen kann. Eine Erklärung antippen lässt ihren Punkt
im Bild aufleuchten - und umgekehrt.
========================================================================== */
document.addEventListener("DOMContentLoaded", function () {
const ef = document.getElementById("einfuehrung");
if (!ef) return;
const folien = [...ef.querySelectorAll(".ef-folie")];
const zurueck = ef.querySelector(".ef-zurueck");
const weiter = ef.querySelector(".ef-weiter");
const zaehler = ef.querySelector(".ef-zaehler");
const balken = ef.querySelector(".ef-fortschritt span");
const kapitelWahl = ef.querySelector(".ef-kapitel");
const punkteNav = ef.querySelector(".ef-punkte-nav");
let aktuell = 0;
// Die Punkte unten: einer je Folie, Teile durch eine Lücke getrennt.
folien.forEach((f, i) => {
const p = document.createElement("span");
p.className = "ef-punkt" + (i > 0 && folien[i - 1].dataset.teil !== f.dataset.teil ? " neuer-teil" : "");
p.dataset.ziel = i;
punkteNav.appendChild(p);
});
punkteNav.removeAttribute("aria-hidden");
function zeige(nr, ohneAdresse) {
nr = Math.max(0, Math.min(folien.length - 1, nr));
const richtung = nr >= aktuell ? "vor" : "rueck";
folien.forEach((f, i) => {
f.hidden = i !== nr;
f.classList.remove("rein-vor", "rein-rueck");
});
const f = folien[nr];
void f.offsetWidth; // Animation neu starten
f.classList.add("rein-" + richtung);
aktuell = nr;
zaehler.textContent = (nr + 1) + " / " + folien.length;
balken.style.width = ((nr + 1) / folien.length * 100).toFixed(1) + "%";
zurueck.disabled = nr === 0;
weiter.innerHTML = nr === folien.length - 1
? 'Von vorn<i class="bi bi-arrow-counterclockwise ms-1"></i>'
: 'Weiter<i class="bi bi-arrow-right ms-1"></i>';
ef.querySelectorAll(".ef-teil").forEach(t => t.classList.toggle("aktiv", t.dataset.teil === f.dataset.teil));
punkteNav.querySelectorAll(".ef-punkt").forEach((p, i) => p.classList.toggle("aktiv", i === nr));
// Das Kapitel dieser Folie in der Auswahl: die letzte Kapitel-Folie <= nr.
let kap = null;
[...kapitelWahl.options].forEach(o => { if (Number(o.value) <= nr) kap = o.value; });
if (kap !== null) kapitelWahl.value = kap;
if (!ohneAdresse) history.replaceState(null, "", "#folie-" + (nr + 1));
// Auf dem Handy nach oben, damit die neue Folie mit dem Titel beginnt.
const oben = ef.getBoundingClientRect().top;
if (oben < 0) ef.scrollIntoView({ block: "start", behavior: "smooth" });
}
zurueck.addEventListener("click", () => zeige(aktuell - 1));
weiter.addEventListener("click", () => zeige(aktuell === folien.length - 1 ? 0 : aktuell + 1));
kapitelWahl.addEventListener("change", () => zeige(Number(kapitelWahl.value)));
ef.querySelectorAll(".ef-teil").forEach(t => t.addEventListener("click", () => {
zeige(folien.findIndex(f => f.dataset.teil === t.dataset.teil));
}));
punkteNav.addEventListener("click", e => {
const p = e.target.closest(".ef-punkt");
if (p) zeige(Number(p.dataset.ziel));
});
document.addEventListener("keydown", e => {
if (e.target.closest("input, select, textarea")) return;
if (e.key === "ArrowRight" || e.key === "PageDown") { zeige(aktuell + 1); e.preventDefault(); }
if (e.key === "ArrowLeft" || e.key === "PageUp") { zeige(aktuell - 1); e.preventDefault(); }
if (e.key === "Home") zeige(0);
if (e.key === "End") zeige(folien.length - 1);
});
// Wischen: waagerecht deutlich, senkrecht wenig - sonst ist es Scrollen.
let start = null;
ef.querySelector(".ef-buehne").addEventListener("touchstart", e => {
start = { x: e.touches[0].clientX, y: e.touches[0].clientY };
}, { passive: true });
ef.querySelector(".ef-buehne").addEventListener("touchend", e => {
if (!start) return;
const dx = e.changedTouches[0].clientX - start.x;
const dy = e.changedTouches[0].clientY - start.y;
start = null;
if (Math.abs(dx) > 60 && Math.abs(dx) > Math.abs(dy) * 1.5) zeige(aktuell + (dx < 0 ? 1 : -1));
}, { passive: true });
// Erklärung und Punkt im Bild gehören zusammen: das eine hebt das andere hervor.
function hervorheben(folie, nr, an) {
folie.querySelectorAll('[data-marke="' + nr + '"]').forEach(el => el.classList.toggle("hell", an));
}
folien.forEach(folie => {
folie.addEventListener("mouseover", e => {
const el = e.target.closest("[data-marke]");
if (el) hervorheben(folie, el.dataset.marke, true);
});
folie.addEventListener("mouseout", e => {
const el = e.target.closest("[data-marke]");
if (el) hervorheben(folie, el.dataset.marke, false);
});
folie.addEventListener("click", e => {
const el = e.target.closest("[data-marke]");
if (!el) return;
const nr = el.dataset.marke;
folie.querySelectorAll("[data-marke]").forEach(x => x.classList.remove("hell"));
hervorheben(folie, nr, true);
// Auf dem Handy liegt das Bild über dem Text: zum Punkt scrollen.
const punkt = folie.querySelector('.ef-marke[data-marke="' + nr + '"]');
if (punkt && el.tagName === "LI") {
const r = punkt.getBoundingClientRect();
if (r.top < 60 || r.bottom > window.innerHeight) punkt.scrollIntoView({ block: "center", behavior: "smooth" });
}
});
});
// Ein Link auf eine Folie in einem schon offenen Tab ändert nur den Anker -
// die Seite lädt dann nicht neu, also hier nachziehen.
window.addEventListener("hashchange", () => {
const t = /^#folie-(\d+)$/.exec(location.hash);
if (t && Number(t[1]) - 1 !== aktuell) zeige(Number(t[1]) - 1, true);
});
const treffer = /^#folie-(\d+)$/.exec(location.hash);
zeige(treffer ? Number(treffer[1]) - 1 : 0, !treffer);
});