purinto-website/assets/js/stueckzahl-achse.js
Denise Krölling 1a63f66463 Website der Purinto GmbH aufsetzen
Statische Website ohne Build-Schritt: reines HTML, CSS und Vanilla-JS,
lauffaehig auf beliebigem Webspace.

Neun Seiten: Startseite, Dienstleistungen, Projekte, Ueber uns, Kontakt
sowie Impressum, Datenschutz, Danke-Seite und 404.

Kernstueck der Startseite ist die Stueckzahl-Achse: ein logarithmischer
Regler von 1 bis 100.000 Stueck, der zur gewaehlten Menge Verfahren,
Werkzeugkosten, Lieferzeit und Anwendungsfall ausgibt. Die dritte Zone
ist optisch zurueckgenommen, weil Serienspritzguss ueber Partnerbetriebe
laeuft und nicht zum eigenen Leistungsumfang gehoert. Der Leistungsumfang
ist durchgehend auf FDM, SLA und SLA-gedruckte Formeinsaetze beschraenkt.

Gestaltung nach Vorgabe: Anthrazit, Salbeigruen und Flieder als Akzent.
Archivo, Public Sans und IBM Plex Mono werden lokal ausgeliefert statt
ueber die Google-CDN eingebunden.

Kontaktformular ueber Web3Forms mit Client-Validierung; der Access-Key
ist noch ein Platzhalter. Alle Firmen- und Projektinhalte sind sichtbar
als Platzhalter markiert, die Uebergabe-Checkliste steht in der README.

Geprueft: WCAG-AA-Kontraste auf allen Seiten, kein horizontaler Ueberlauf
von 320 bis 1920 px, Tastaturbedienung der Achse, feste Zeilenzahl der
Hero-Headline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 13:07:08 +02:00

272 lines
9.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ==========================================================================
Purinto GmbH — Stückzahl-Achse
Das Signature-Element: eine logarithmische Achse von 1 bis 100.000 Stück.
Der Besucher wählt eine Stückzahl und sieht, welches Verfahren dafür das
richtige ist — inklusive der ehrlichen Grenze, ab der Purinto mit
Partnerbetrieben arbeitet statt selbst zu fertigen.
ALLE fachlichen Eckwerte stehen in ZONEN. Wer die Grenzen ändern will,
ändert sie hier — Achse, Beschriftung und Tafel ziehen automatisch nach.
========================================================================== */
(function () {
"use strict";
var wurzel = document.querySelector("[data-achse]");
if (!wurzel) return;
/* ----------------------------------------------------------------------
Fachliche Datenbasis — vor Livegang bestätigen.
"bis" ist jeweils die obere Grenze der Zone in Stück.
---------------------------------------------------------------------- */
var MIN = 1;
var MAX = 100000;
var ZONEN = [
{
id: "druck",
bis: 100,
kurz: "FDM · SLA",
verfahren: "FDM oder SLA — direkt gedruckt",
partner: false,
werkzeug: "Kein Werkzeug nötig",
lieferzeit: "1–5 Arbeitstage",
stueckkosten: "Über die Menge konstant",
anwendung:
"Funktionsmuster, Passungs- und Einbauprüfungen, Vorrichtungen, Designfreigaben",
hinweis:
"In diesem Bereich fertigen wir direkt aus der CAD-Datei. Änderungen kosten nichts außer einem neuen Druck — genau richtig, solange die Konstruktion noch in Bewegung ist."
},
{
id: "bridge",
bis: 1000,
kurz: "Bridge Tooling",
verfahren: "Bridge Tooling — SLA-gedruckter Formeinsatz",
partner: false,
werkzeug: "Gering — der Formeinsatz wird gedruckt, nicht gefräst",
lieferzeit: "2–3 Wochen bis zum ersten Schuss",
stueckkosten: "Deutlich unter dem Direktdruck",
anwendung:
"Vorserien, Markttests, Nullserien und Zulassungsmuster im späteren Serienwerkstoff",
hinweis:
"Hier liegt unser Schwerpunkt. Sie bekommen Teile im echten Serienwerkstoff mit Spritzguss-Eigenschaften, ohne vorher ein Stahlwerkzeug zu finanzieren."
},
{
id: "serie",
bis: MAX,
kurz: "Serienspritzguss",
verfahren: "Serienspritzguss — Stahlwerkzeug",
partner: true,
werkzeug: "Hoch — Stahlwerkzeug als einmalige Investition",
lieferzeit: "8–14 Wochen bis zum ersten Schuss",
stueckkosten: "Am niedrigsten, sobald die Menge das Werkzeug trägt",
anwendung: "Serienfertigung nach abgeschlossener Entwicklung",
hinweis:
"Ab hier ist ein Stahlwerkzeug wirtschaftlich. Das ist nicht unser Kerngeschäft — wir begleiten den Übergang und vermitteln an lokale Spritzgießer, mit denen wir zusammenarbeiten."
}
];
var START = 40; // Stückzahl beim Laden der Seite
/* ----------------------------------------------------------------------
Umrechnung zwischen Stückzahl und Position auf der Achse
---------------------------------------------------------------------- */
var SPANNE = Math.log10(MAX) - Math.log10(MIN);
function wertZuAnteil(wert) {
return (Math.log10(wert) - Math.log10(MIN)) / SPANNE;
}
function anteilZuWert(anteil) {
return Math.pow(10, Math.log10(MIN) + anteil * SPANNE);
}
// Auf lesbare Stufen runden — "37 Stück" hilft niemandem bei "etwa 40".
function runden(wert) {
if (wert < 10) return Math.max(MIN, Math.round(wert));
if (wert < 100) return Math.round(wert / 5) * 5;
if (wert < 1000) return Math.round(wert / 10) * 10;
if (wert < 10000) return Math.round(wert / 100) * 100;
return Math.round(wert / 1000) * 1000;
}
function zoneFuer(wert) {
for (var i = 0; i < ZONEN.length; i++) {
if (wert <= ZONEN[i].bis) return ZONEN[i];
}
return ZONEN[ZONEN.length - 1];
}
// Bewusst nicht toLocaleString: de-AT trennt Tausender mit einem schmalen
// Leerzeichen ("100 000"), der übrige Seitentext schreibt "100.000".
// Explizite Formatierung hält beides deckungsgleich.
function formatieren(zahl) {
return String(zahl).replace(/\B(?=(\d{3})+(?!\d))/g, ".");
}
/* ----------------------------------------------------------------------
Elemente
---------------------------------------------------------------------- */
var spur = wurzel.querySelector("[data-spur]");
var griff = wurzel.querySelector("[data-griff]");
var zonenElemente = wurzel.querySelectorAll("[data-zone]");
var ausgabe = document.querySelector("[data-ausgabe]");
var verfahrenAus = document.querySelector("[data-verfahren]");
var hinweisAus = document.querySelector("[data-hinweis]");
var werkzeugAus = document.querySelector("[data-werkzeug]");
var lieferzeitAus = document.querySelector("[data-lieferzeit]");
var stueckkostenAus = document.querySelector("[data-stueckkosten]");
var anwendungAus = document.querySelector("[data-anwendung]");
if (!spur || !griff) return;
/* ----------------------------------------------------------------------
Zonenbreiten aus den Daten ableiten, damit geänderte Grenzen
sofort auch optisch stimmen
---------------------------------------------------------------------- */
(function zonenAufbauen() {
var vorher = 0;
ZONEN.forEach(function (zone, i) {
var ende = wertZuAnteil(zone.bis) * 100;
var breite = ende - vorher;
if (zonenElemente[i]) zonenElemente[i].style.width = breite + "%";
var flaeche = wurzel.querySelector('[data-flaeche="' + zone.id + '"]');
if (flaeche) {
flaeche.style.left = vorher + "%";
flaeche.style.right = 100 - ende + "%";
}
vorher = ende;
});
// Skalenbeschriftungen aus derselben Umrechnung setzen, damit sie
// exakt über den Zonengrenzen stehen.
var skala = wurzel.querySelectorAll("[data-skala]");
Array.prototype.forEach.call(skala, function (el) {
var wert = Number(el.getAttribute("data-skala"));
if (!wert) return;
el.style.left = wertZuAnteil(wert) * 100 + "%";
});
})();
/* ----------------------------------------------------------------------
Zustand setzen
---------------------------------------------------------------------- */
var aktuellerWert = START;
function setzen(wert) {
wert = Math.min(MAX, Math.max(MIN, runden(wert)));
aktuellerWert = wert;
var anteil = wertZuAnteil(wert);
var zone = zoneFuer(wert);
griff.style.left = anteil * 100 + "%";
griff.setAttribute("aria-valuenow", String(wert));
griff.setAttribute(
"aria-valuetext",
formatieren(wert) + " Stück, " + zone.verfahren
);
Array.prototype.forEach.call(zonenElemente, function (el) {
el.classList.toggle("ist-aktiv", el.getAttribute("data-zone") === zone.id);
});
if (ausgabe) ausgabe.textContent = formatieren(wert);
if (verfahrenAus) verfahrenAus.textContent = zone.verfahren;
if (hinweisAus) hinweisAus.textContent = zone.hinweis;
if (werkzeugAus) werkzeugAus.textContent = zone.werkzeug;
if (lieferzeitAus) lieferzeitAus.textContent = zone.lieferzeit;
if (stueckkostenAus) stueckkostenAus.textContent = zone.stueckkosten;
if (anwendungAus) anwendungAus.textContent = zone.anwendung;
}
/* ----------------------------------------------------------------------
Zeigerbedienung
---------------------------------------------------------------------- */
function ausPosition(clientX) {
var kasten = spur.getBoundingClientRect();
if (!kasten.width) return;
var anteil = (clientX - kasten.left) / kasten.width;
setzen(anteilZuWert(Math.min(1, Math.max(0, anteil))));
}
var zieht = false;
spur.addEventListener("pointerdown", function (e) {
zieht = true;
spur.setPointerCapture(e.pointerId);
ausPosition(e.clientX);
griff.focus();
e.preventDefault();
});
spur.addEventListener("pointermove", function (e) {
if (zieht) ausPosition(e.clientX);
});
var beenden = function (e) {
if (!zieht) return;
zieht = false;
if (spur.hasPointerCapture && spur.hasPointerCapture(e.pointerId)) {
spur.releasePointerCapture(e.pointerId);
}
};
spur.addEventListener("pointerup", beenden);
spur.addEventListener("pointercancel", beenden);
/* ----------------------------------------------------------------------
Tastaturbedienung — der Griff ist ein role="slider"
---------------------------------------------------------------------- */
var SCHRITT = 0.02; // Anteil der Gesamtachse pro Tastendruck
griff.addEventListener("keydown", function (e) {
var anteil = wertZuAnteil(aktuellerWert);
var neu = null;
switch (e.key) {
case "ArrowRight":
case "ArrowUp":
neu = anteil + SCHRITT;
break;
case "ArrowLeft":
case "ArrowDown":
neu = anteil - SCHRITT;
break;
case "PageUp":
neu = anteil + SCHRITT * 5;
break;
case "PageDown":
neu = anteil - SCHRITT * 5;
break;
case "Home":
neu = 0;
break;
case "End":
neu = 1;
break;
default:
return;
}
e.preventDefault();
setzen(anteilZuWert(Math.min(1, Math.max(0, neu))));
});
griff.setAttribute("aria-valuemin", String(MIN));
griff.setAttribute("aria-valuemax", String(MAX));
setzen(START);
})();