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>
272 lines
9.4 KiB
JavaScript
272 lines
9.4 KiB
JavaScript
/* ==========================================================================
|
||
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);
|
||
})();
|