Produktseite mit technischen Daten
Baureihe als echte Variantentabelle mit fester Kopfzeile und Artikelnummer-Spalte; Zeile wählen zieht die Maßzeichnung waagerecht auf die neue Länge, Kenndaten stehen mit Wert, Einheit und Prüfbedingung, die Anfrage übernimmt die Artikelnummer.
Komponenten und Zulieferteile in mehreren Baugrößen (bis etwa acht), bei denen Einkäufer Werte nebeneinander vergleichen.
Eine Produktseite für Maschinen, Komponenten und Zulieferteile ohne Warenkorb. Oben steht die gewählte Variante mit Artikelnummer zum Kopieren, Maßzeichnung und zwei Wegen: „Diese Variante anfragen“ und Datenblatt. Darunter liegen Reiter für technische Daten, Downloads und Zubehör. Die Varianten einer Baureihe stehen als echte Tabelle nebeneinander, mit fester Kopfzeile und fester erster Spalte. Kenndaten haben je Zeile Wert, Einheit und Prüfbedingung. Wer eine Zeile wählt, sieht Maßzeichnung und Kenndaten ohne Sprung wechseln. „Nur Unterschiede“ blendet gleiche Spalten aus, ein Schalter rechnet in Zoll und Pfund um. Die Anfrage übernimmt die Artikelnummer.
Einkäufer und Ingenieure kommen mit einer Aufgabe: Werte vergleichen, die passende Baugröße finden, genau diese anfragen oder in die Konstruktion übernehmen. Eine Tabelle mit Einheit in eigener Spalte und Prüfbedingung lässt sich schneller lesen und belastbarer zitieren als Werte im Fließtext. Die kopierbare Artikelnummer und die vorausgefüllte Anfrage nehmen die zwei häufigsten Fehlerquellen heraus: abgetippte Nummern und Anfragen ohne Variante.
Wann einsetzen
Maschinen- und Anlagenbau, Zulieferer, Antriebs-, Mess- und Vakuumtechnik, Werkzeuge und Komponenten: überall, wo eine Baureihe mehrere Baugrößen hat und der Preis auf Anfrage kommt. Auch für technische Konsumgüter mit vielen Kennwerten, wenn statt Warenkorb ein Händler oder eine Fachperson folgt.
Wann nicht
Nicht für Shops mit Preis und Warenkorb (dafür produkt-kaufbox-varianten). Nicht für Immobilien und Fahrzeuge mit gesetzlichen Pflichtangaben (objekt-steckbrief-expose). Nicht für Pakete und Tarife (vergleichstabelle-sticky). Ab etwa zehn Varianten oder mehreren Merkmalen gehört ein Produktfinder davor (facettenfilter-ergebnisliste), die Tabelle zeigt dann nur die Treffer.
Varianten
- Baureihe: bis etwa acht Varianten als Tabelle nebeneinander, Zeile wählen wechselt Zeichnung und Kenndaten (Komponenten in mehreren Baugrößen)
- Einzel: eine Variante je Seite mit Zubehör und Ersatzteilen, andere Baugrößen nur als Sprungliste (wie bei Schmalz je Artikelnummer)
- Konfiguriert: Merkmale wählen, daraus entsteht die passende Artikelnummer; unmögliche Kombinationen sind begründet gesperrt; ein externer Konfigurator öffnet erst nach Klick
Umsetzung
Die Daten liegen einmal als Objekt je Variante vor (Werte immer in SI-Einheiten, Text-Werte getrennt). Daraus entstehen Variantentabelle, Kenndaten, Maßzeichnung, Maßliste und JSON-LD. Variantentabelle: <table> mit <caption>, <th scope="col"> mit Einheit in eigener Zeile, erste Zelle je Zeile ein <th scope="row"> mit Radio und Artikelnummer. Wrapper mit overflow: auto und max-height; thead th { position: sticky; top: 0 } und die erste Spalte { position: sticky; left: 0 }. „Nur Unterschiede“ blendet Spalten aus, deren Werte in allen Varianten gleich sind. Kenndaten: Merkmal, Wert (rechtsbündig, Tabellenziffern), Einheit, Prüfbedingung; leere Werte erzeugen keine Zeile. Zahlen nur über Intl.NumberFormat der Seitensprache, Umrechnung metrisch zu imperial erst bei der Ausgabe. Maßzeichnung als inline-SVG mit role="img" und Verweis auf eine sichtbare Liste der Maße. Variantenwechsel: Länge der Zeichnung wird nur waagerecht nachgeführt, Tabellen tauschen Werte ohne Animation. Artikelnummer mit navigator.clipboard und Rückfall. „Diese Variante anfragen“ öffnet ein <dialog> mit der Artikelnummer als festem Feld. JSON-LD Product (sku, mpn, additionalProperty als PropertyValue mit unitCode) wird bei jedem Wechsel aus denselben Daten neu geschrieben und enthält nur sichtbare Werte. Druckansicht über @media print und denselben Zustand am Bildschirm. CAD-Viewer und Konfigurator von Dritten laden erst nach Klick.
Robust umsetzen
Telefon. Die Variantentabelle scrollt in ihrem Rahmen waagerecht, die Artikelnummer bleibt links stehen. Reiter bleiben eine Zeile und dürfen umbrechen. Maßzeichnung über volle Breite, darunter die Maße als Liste. Anfrage-Knopf mindestens 44 px hoch.
Ohne Bewegung. Die Zeichnung springt auf die neue Länge, Reiter und Zeilenmarke wechseln ohne Übergang. Werte wechseln ohnehin ohne Animation.
Barrierefrei. Tabellen mit caption, th scope="col" und scope="row" (WCAG 1.3.1). Auswahl der Variante über echte Radios in der ersten Spalte, nicht nur über Klick auf die Zeile. Gewählte Zeile mit Balken und Text „gewählt“, nicht nur Farbe (1.4.1). Der waagerecht scrollende Tabellenrahmen ist fokussierbar und benannt (2.1.1). Reiter nach dem APG-Muster mit manueller Aktivierung (Pfeile bewegen den Fokus, Eingabe oder Leertaste wählt). Maßzeichnung mit Text-Alternative als sichtbare Liste der Maße (1.1.1). Kopieren meldet „Artikelnummer kopiert“ über role="status" (4.1.3). Anfrage als modales dialog mit Überschrift und Esc.
Code
<!DOCTYPE html>
<!--
Produktseite mit technischen Daten
Fundus · Sektionen & Funktionen · https://cecodesign.de/lab/fundus/sektionen/technische-daten-produktseite/
Eigenständige Datei. Bilder liegen hier unter bilder/ – eigene einsetzen.
-->
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Produktseite mit technischen Daten · Fundus-Demo</title>
<script>document.documentElement.classList.add('js');if(matchMedia('(prefers-reduced-motion: reduce)').matches)document.documentElement.classList.add('reduziert')</script>
<script type="application/ld+json" id="jsonld">{}</script>
<style>
/* Welt: Rezept „industrie-b2b-mittelstand“ – Vahlberg Vakuumtechnik GmbH, Olpe (erfunden) */
@font-face { font-family: 'Strichpunkt Sans'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/strichpunkt-sans:vf@latest/latin-full-normal.woff2') format('woff'); font-weight: 400 900; font-stretch: 100% 200%; font-display: swap; }
@font-face { font-family: 'Archivo'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/archivo:vf@latest/latin-full-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-stretch: 62% 125%; font-display: swap; }
:root {
/* Farbe aus dem Rezept: Blechgrau, Graphit, Stahlblau, genau ein Signal */
--grund: #f3f3f0; --papier: #fbfbf9; --text: #111417; --leise: #4e555b; --kopfzelle: #e4e5e1;
--raster: rgba(17, 20, 23, .13); --hilfe: rgba(17, 20, 23, .055);
--stahl: #16202a; --signal: #0e518d; --signal-tief: #0a3f6f; --signal-hell: #e3eaf1;
--warn: #8a4b00; --warn-flaeche: #f6ecdc;
/* token-swiss-knackig */
--ease: cubic-bezier(0.25, 1, 0.5, 1); --ease-aus: cubic-bezier(0.5, 0, 0.75, 0);
--d1: 200ms; --d2: 350ms; --d1-aus: 140ms; --d2-aus: 245ms;
--cols: 12;
}
@media (max-width: 820px) { :root { --cols: 8; } }
@media (max-width: 640px) { :root { --cols: 4; } }
* { box-sizing: border-box; margin: 0; padding: 0; }
[hidden] { display: none !important; }
html { background: var(--grund); color: var(--text); font: 400 1.0625rem/1.55 'Archivo', system-ui, sans-serif; font-stretch: 100%; -webkit-font-smoothing: antialiased; }
:root:has(:focus-visible) { scroll-behavior: auto; }
body { min-height: 100svh; overflow-x: clip; }
button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }
a { color: inherit; }
:focus-visible { outline: 2px solid var(--signal); outline-offset: 2px; }
.ziffern, td, .wert { font-variant-numeric: tabular-nums lining-nums; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
/* Normschrift-Ersatz: Archivo schmal, versal (Rezept statt Mono) */
.code { font-size: .75rem; font-weight: 600; font-stretch: 75%; letter-spacing: .08em; text-transform: uppercase; color: var(--leise); }
/* ── Kopf ─────────────────────────────────────────── */
.kopf { background: var(--stahl); color: #fff; }
.kopf__innen { max-width: 1400px; margin: 0 auto; display: flex; align-items: center; gap: 12px 32px; padding: 0 clamp(16px, 3vw, 40px); min-height: 64px; flex-wrap: wrap; }
.marke { display: flex; align-items: center; gap: 12px; text-decoration: none; }
.marke svg { width: 30px; height: 30px; flex: none; }
.marke b { font: 600 1.05rem/1 'Strichpunkt Sans', 'Archivo', sans-serif; font-stretch: 130%; letter-spacing: .02em; }
.marke span { display: block; font-size: .72rem; font-stretch: 75%; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; color: rgba(255, 255, 255, .72); margin-top: 3px; }
.kopf nav ul { display: flex; gap: 26px; list-style: none; font-size: .9375rem; }
.kopf nav a { text-decoration: none; color: rgba(255, 255, 255, .86); padding: 8px 0; }
.kopf nav a[aria-current] { color: #fff; box-shadow: inset 0 -2px 0 #fff; }
.kopf nav a:hover { color: #fff; }
.kopf__rechts { margin-left: auto; display: flex; align-items: center; gap: 20px; font-size: .875rem; }
.sprache { color: rgba(255, 255, 255, .86); text-decoration: none; }
/* ── Raster (Signatur blueprint-raster-sichtbar) ─── */
.achse { max-width: 1400px; margin: 0 auto; padding: 0 clamp(16px, 3vw, 40px); }
.achse ol { display: grid; grid-template-columns: repeat(var(--cols), minmax(0, 1fr)); list-style: none; border-left: 1px solid var(--raster); }
.achse li { border-right: 1px solid var(--raster); padding: 6px 8px 4px; font-size: .6875rem; font-weight: 600; font-stretch: 75%; letter-spacing: .08em; color: var(--leise); }
@media (max-width: 820px) { .achse li:nth-child(n+9) { display: none; } }
@media (max-width: 640px) { .achse li:nth-child(n+5) { display: none; } }
.blatt { position: relative; max-width: 1400px; margin: 0 auto; padding: 0 clamp(16px, 3vw, 40px) 64px; }
.blatt__innen { position: relative; border-left: 1px solid var(--raster); border-right: 1px solid var(--raster); }
.blatt__innen::before { content: ''; position: absolute; inset: 0; pointer-events: none; background: repeating-linear-gradient(to right, transparent 0 calc(100% / var(--cols) - 1px), var(--hilfe) calc(100% / var(--cols) - 1px) calc(100% / var(--cols))); }
.zone { position: relative; display: grid; grid-template-columns: repeat(var(--cols), minmax(0, 1fr)); border-top: 1px solid var(--raster); }
/* Plus-Marker nur an Schnittpunkten: Abschnittslinie × Außenkanten */
.zone::before, .zone::after { content: ''; position: absolute; top: -6px; width: 11px; height: 11px; background: linear-gradient(var(--text), var(--text)) center / 11px 1px no-repeat, linear-gradient(var(--text), var(--text)) center / 1px 11px no-repeat; opacity: .6; }
.zone::before { left: -6px; } .zone::after { right: -6px; }
.zone > * { grid-column: 1 / -1; min-width: 0; }
.abschnitt-code { padding: 14px clamp(14px, 1.6vw, 24px) 0; }
.pfad { padding: 12px clamp(14px, 1.6vw, 24px); font-size: .8125rem; color: var(--leise); }
.pfad ol { display: flex; flex-wrap: wrap; gap: 4px 10px; list-style: none; }
.pfad li + li::before { content: '/'; margin-right: 10px; opacity: .6; }
.pfad a { text-decoration: none; }
.pfad a:hover { text-decoration: underline; }
/* ── Produktkopf ─────────────────────────────────── */
.intro { padding: 10px clamp(14px, 1.6vw, 24px) 28px; display: grid; gap: 18px; align-content: start; }
h1 { font: 600 clamp(2.1rem, 1.3rem + 2.5vw, 3.5rem)/.98 'Strichpunkt Sans', 'Archivo', sans-serif; font-stretch: 114%; letter-spacing: -.03em; hyphens: auto; text-wrap: balance; }
h1 small { display: block; font: 400 1.0625rem/1.4 'Archivo', sans-serif; font-stretch: 100%; letter-spacing: 0; color: var(--leise); margin-top: 12px; }
.lead { max-width: 52ch; text-wrap: pretty; }
.artikel { display: flex; align-items: stretch; flex-wrap: wrap; border: 1px solid var(--raster); background: var(--papier); width: fit-content; max-width: 100%; }
.artikel__text { padding: 8px 14px; display: grid; gap: 2px; border-right: 1px solid var(--raster); }
.artikel__text b { font-size: 1.125rem; font-weight: 600; font-stretch: 87%; font-variant-numeric: tabular-nums; letter-spacing: .02em; }
.kopieren { position: relative; display: inline-flex; align-items: center; gap: 8px; min-height: 44px; padding: 0 14px; font-size: .875rem; color: var(--leise); }
.kopieren:hover { color: var(--text); background: var(--kopfzelle); }
.kopieren svg { width: 18px; height: 18px; flex: none; }
.kopieren .ic-ok { position: absolute; left: 14px; opacity: 0; }
.kopieren .ic-ok path { stroke-dasharray: 1; stroke-dashoffset: 1; }
.kopieren[data-ok] .ic-kopie { opacity: 0; }
.kopieren[data-ok] .ic-ok { opacity: 1; }
.kopieren[data-ok] .ic-ok path { stroke-dashoffset: 0; transition: stroke-dashoffset 300ms var(--ease) 80ms; }
.kopieren[data-ok] { color: var(--signal); }
.kurzwerte { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); border-top: 1px solid var(--text); }
.kurzwerte div { padding: 10px 12px 0 0; }
.kurzwerte dt { font-size: .8125rem; color: var(--leise); }
.kurzwerte dd { font-size: 1.375rem; font-weight: 600; font-stretch: 87%; font-variant-numeric: tabular-nums; line-height: 1.2; }
.kurzwerte dd span { font-size: .875rem; font-weight: 400; color: var(--leise); margin-left: 4px; }
.knoepfe { display: flex; flex-wrap: wrap; gap: 12px; }
/* Buttons: button-zustaende (gefüllt) + kontur-button-zeichnen, Variante ecken */
.btn { position: relative; display: inline-flex; align-items: center; justify-content: center; gap: 10px; min-height: 48px; padding: 0 22px; font-size: .9375rem; font-weight: 600; text-decoration: none; border-radius: 0; transition: background-color var(--d1) var(--ease), color var(--d1) var(--ease); }
.btn--voll { background: var(--signal); color: #fff; }
.btn--voll:hover { background: var(--signal-tief); }
.btn--voll:active { transform: translateY(1px); }
.btn--voll:focus-visible { outline-offset: 3px; }
.btn .pfeil { width: 16px; height: 16px; transition: transform var(--d1) var(--ease); }
.btn--voll:hover .pfeil { transform: translateX(4px); }
.btn--kontur { color: var(--text); box-shadow: inset 0 0 0 1px rgba(17, 20, 23, .4); background: transparent; }
.btn--kontur:hover { background: rgba(17, 20, 23, .04); }
.ecke { position: absolute; width: 10px; height: 10px; border: 0 solid var(--text); pointer-events: none; transition: width var(--d1-aus) var(--ease-aus), height var(--d1-aus) var(--ease-aus); }
.ecke--ol { top: 0; left: 0; border-top-width: 1.5px; border-left-width: 1.5px; }
.ecke--or { top: 0; right: 0; border-top-width: 1.5px; border-right-width: 1.5px; }
.ecke--ul { bottom: 0; left: 0; border-bottom-width: 1.5px; border-left-width: 1.5px; }
.ecke--ur { bottom: 0; right: 0; border-bottom-width: 1.5px; border-right-width: 1.5px; }
@media (hover: hover) { .btn--kontur:hover .ecke { width: 50%; height: 50%; transition: width var(--d2) var(--ease), height var(--d2) var(--ease); } }
.btn--kontur:focus-visible .ecke { width: 50%; height: 50%; }
.btn small { font-weight: 400; font-stretch: 87%; color: var(--leise); }
.btn--voll small { color: rgba(255, 255, 255, .8); }
.btn[data-zustand="laedt"] { pointer-events: none; }
.btn .kreisel { width: 16px; height: 16px; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: dreh 600ms linear infinite; display: none; }
.btn[data-zustand="laedt"] .kreisel { display: block; }
.btn[data-zustand="laedt"] .pfeil { display: none; }
@keyframes dreh { to { transform: rotate(1turn); } }
/* ── Maßzeichnung ────────────────────────────────── */
.zeichnung { padding: 10px clamp(14px, 1.6vw, 24px) 24px; display: grid; gap: 12px; align-content: start; }
.blattfeld { position: relative; background: var(--papier); border: 1px solid var(--raster); }
.blattfeld svg { display: block; width: 100%; height: auto; }
.zeichnung .kante { fill: none; stroke: var(--text); stroke-width: 1.6; stroke-linejoin: miter; }
.zeichnung .flaeche { fill: #eceeea; stroke: var(--text); stroke-width: 1.6; }
.zeichnung .schaum { fill: url(#schraffur); stroke: var(--text); stroke-width: 1.2; }
.zeichnung .duenn { fill: none; stroke: var(--text); stroke-width: .8; }
.zeichnung .mittel { fill: none; stroke: var(--leise); stroke-width: .8; stroke-dasharray: 14 3 2 3; }
.zeichnung .loch { fill: var(--papier); stroke: var(--text); stroke-width: .9; }
.zeichnung .pfeilspitze { fill: var(--text); }
.mass { position: absolute; transform: translate(-50%, -100%); padding: 0 4px; font-size: .8125rem; font-weight: 600; font-stretch: 87%; font-variant-numeric: tabular-nums; background: var(--papier); line-height: 1.3; white-space: nowrap; }
.mass--senk { transform: translate(6px, -50%); }
.ansicht { position: absolute; max-width: 32%; line-height: 1.3; font-size: .6875rem; font-weight: 600; font-stretch: 75%; letter-spacing: .08em; text-transform: uppercase; color: var(--leise); }
.zeichnung .geist { fill: none; stroke: var(--leise); stroke-width: .8; stroke-dasharray: 4 4; }
.zeichnung .trenn { stroke: var(--raster); stroke-width: 1; }
.zeichnung .kammer { fill: var(--papier); stroke: var(--text); stroke-width: 1.2; }
.zeichnung .kugel { fill: var(--kopfzelle); stroke: var(--text); stroke-width: 1.2; }
.zeichnung .schaum-grob { fill: url(#schraffur-grob); stroke: var(--text); stroke-width: 1.2; }
.legende { position: absolute; transform: translateX(-50%); background: var(--papier); padding: 0 4px; font-size: .6875rem; font-weight: 600; font-stretch: 75%; letter-spacing: .04em; color: var(--leise); white-space: nowrap; }
.zeichnung figcaption { font-size: .8125rem; color: var(--leise); }
.massliste { display: flex; flex-wrap: wrap; gap: 4px 18px; list-style: none; font-size: .875rem; font-variant-numeric: tabular-nums; }
.massliste b { font-weight: 600; }
/* Andere Baugrößen (Variante einzel) */
.groessen { display: none; }
.groessen ul { display: flex; flex-wrap: wrap; gap: 6px; list-style: none; margin-top: 8px; }
.groessen a { display: inline-flex; align-items: center; min-height: 44px; padding: 0 12px; font-size: .875rem; font-stretch: 87%; font-variant-numeric: tabular-nums; text-decoration: none; box-shadow: inset 0 0 0 1px var(--raster); background: var(--papier); }
.groessen a:hover { box-shadow: inset 0 0 0 1px var(--text); }
.groessen a[aria-current] { background: var(--text); color: #fff; box-shadow: none; }
/* Konfigurator (Variante konfiguriert) */
.konfig { display: none; gap: 14px; padding: 0 clamp(14px, 1.6vw, 24px) 24px; align-content: start; }
.konfig fieldset { border: 0; display: grid; gap: 6px; min-width: 0; }
.konfig legend { font-size: .8125rem; color: var(--leise); margin-bottom: 4px; }
.wahl { display: flex; flex-wrap: wrap; gap: 6px; }
.wahl label { position: relative; display: inline-flex; align-items: center; min-height: 44px; padding: 0 12px; font-size: .875rem; font-stretch: 87%; font-variant-numeric: tabular-nums; cursor: pointer; background: var(--papier); box-shadow: inset 0 0 0 1px var(--raster); transition: box-shadow var(--d1) var(--ease); }
.wahl input { position: absolute; opacity: 0; inset: 0; cursor: pointer; }
.wahl label:hover { box-shadow: inset 0 0 0 1px var(--text); }
.wahl label:has(input:checked) { background: var(--text); color: #fff; box-shadow: none; }
.wahl label:has(input:focus-visible) { outline: 2px solid var(--signal); outline-offset: 2px; }
.wahl label:has(input:disabled) { color: rgba(17, 20, 23, .45); background: transparent; box-shadow: inset 0 0 0 1px var(--raster); cursor: not-allowed; text-decoration: line-through; }
.wahl label:has(input:disabled) input { cursor: not-allowed; }
.grund { font-size: .8125rem; color: var(--leise); }
.extern { border-top: 1px solid var(--raster); padding-top: 12px; display: grid; gap: 10px; font-size: .875rem; }
.extern[hidden] { display: none; }
.extern p { color: var(--leise); }
.extern code { font: 600 .8125rem/1.4 'Archivo', sans-serif; font-stretch: 87%; word-break: break-all; color: var(--text); }
.markiert { display: inline-block; font-size: .6875rem; font-weight: 600; font-stretch: 75%; letter-spacing: .08em; text-transform: uppercase; color: var(--warn); background: var(--warn-flaeche); padding: 2px 6px; }
/* ── Reiter (tabs-pfeiltasten-focusgroup, manuell) ── */
.reiter { display: flex; flex-wrap: wrap; gap: 0 4px; padding: 6px clamp(14px, 1.6vw, 24px) 0; border-bottom: 1px solid var(--raster); }
.reiter [role=tab] { position: relative; min-height: 48px; padding: 0 14px; font-size: 1rem; font-weight: 600; color: var(--leise); }
.reiter [role=tab]::after { content: ''; position: absolute; left: 14px; right: 14px; bottom: -1px; height: 3px; background: var(--signal); transform: scaleX(0); transform-origin: left; transition: transform var(--d1-aus) var(--ease-aus); }
.reiter [role=tab][aria-selected=true] { color: var(--text); }
.reiter [role=tab][aria-selected=true]::after { transform: scaleX(1); transition: transform var(--d1) var(--ease); }
.reiter [role=tab]:hover { color: var(--text); }
.reiter [role=tab] span { font-weight: 400; font-stretch: 87%; margin-left: 4px; font-variant-numeric: tabular-nums; }
[role=tabpanel] { padding: 18px clamp(14px, 1.6vw, 24px) 28px; }
[role=tabpanel][hidden] { display: none; }
/* Werkzeugleiste über der Tabelle */
.leiste { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 22px; margin-bottom: 12px; }
.leiste h2, .block h2 { font: 600 1.25rem/1.2 'Archivo', sans-serif; }
.leiste h2 { margin-right: auto; }
.schalter { display: inline-flex; align-items: center; gap: 10px; min-height: 44px; font-size: .9375rem; cursor: pointer; }
.schalter__spur { position: relative; width: 38px; height: 22px; flex: none; border-radius: 11px; background: #c9ccc8; transition: background-color var(--d1) var(--ease); }
.schalter__spur::after { content: ''; position: absolute; left: 3px; top: 3px; width: 16px; height: 16px; border-radius: 50%; background: #fff; transition: transform var(--d1) var(--ease); }
.schalter[aria-checked=true] .schalter__spur { background: var(--signal); }
.schalter[aria-checked=true] .schalter__spur::after { transform: translateX(16px); }
.segment { display: inline-flex; border: 0; box-shadow: inset 0 0 0 1px var(--raster); background: var(--papier); }
.segment legend { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
.segment label { position: relative; display: inline-flex; align-items: center; min-height: 44px; padding: 0 14px; font-size: .875rem; cursor: pointer; }
.segment input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.segment label:has(input:checked) { background: var(--text); color: #fff; }
.segment label:has(input:focus-visible) { outline: 2px solid var(--signal); outline-offset: 2px; z-index: 1; }
.leiste .btn--kontur { min-height: 44px; padding: 0 16px; font-size: .875rem; }
/* Variantentabelle: feste Kopfzeile, feste erste Spalte */
.rahmen { position: relative; overflow: auto; max-height: 27rem; border: 1px solid var(--raster); background: var(--papier); overscroll-behavior-x: contain; }
.rahmen:focus-visible { outline-offset: 2px; }
table { border-collapse: separate; border-spacing: 0; width: 100%; font-size: .9375rem; font-stretch: 87%; }
caption { text-align: left; }
.varianten th, .varianten td { padding: 9px 14px; border-bottom: 1px solid var(--raster); white-space: nowrap; text-align: right; vertical-align: middle; }
.varianten thead th { position: sticky; top: 0; z-index: 2; background: var(--kopfzelle); font-weight: 600; vertical-align: bottom; line-height: 1.25; border-bottom: 1px solid var(--text); }
.varianten thead th .einheit { display: block; font-weight: 400; color: var(--leise); }
.varianten th[scope=row], .varianten thead th:first-child { position: sticky; left: 0; text-align: left; }
.varianten thead th:first-child { z-index: 3; }
.varianten th[scope=row] { z-index: 1; background: var(--papier); font-weight: 400; box-shadow: inset -1px 0 0 var(--raster); }
.varianten td.text { text-align: left; }
.varianten tbody tr { cursor: pointer; }
.varianten tbody tr:hover td, .varianten tbody tr:hover th { background: #f0f1ee; }
.varianten tbody tr.an td, .varianten tbody tr.an th { background: var(--signal-hell); }
.varianten tbody tr.an th { box-shadow: inset 3px 0 0 var(--signal), inset -1px 0 0 var(--raster); }
.zeilenwahl { display: flex; align-items: center; gap: 10px; cursor: pointer; min-height: 28px; }
.zeilenwahl input { width: 18px; height: 18px; accent-color: var(--signal); flex: none; }
.zeilenwahl b { font-weight: 600; letter-spacing: .02em; }
.zeilenwahl > span > span { color: var(--leise); margin-left: 6px; }
.varianten thead th.text { text-align: left; }
@media (max-width: 640px) { .zeilenwahl > span > span { display: block; margin-left: 0; font-size: .8125rem; line-height: 1.2; } .varianten th, .varianten td { padding: 8px 10px; } }
.leer { color: var(--leise); }
.varianten .aus { display: none; }
.gleich { margin-top: 8px; font-size: .875rem; color: var(--leise); font-variant-numeric: tabular-nums; }
.gleich b { color: var(--text); font-weight: 600; }
/* Kenndaten der gewählten Variante */
.unten { display: grid; grid-template-columns: repeat(var(--cols), minmax(0, 1fr)); gap: 0; margin-top: 26px; }
.block { grid-column: 1 / -1; min-width: 0; }
.block h2 { margin-bottom: 4px; }
.block .unter { font-size: .875rem; color: var(--leise); margin-bottom: 12px; }
@media (min-width: 821px) {
.intro { grid-column: 1 / span 5; }
.zeichnung { grid-column: 6 / -1; grid-row: 2; border-left: 1px solid var(--raster); }
.intro { grid-row: 2 / span 2; }
.konfig { grid-column: 6 / -1; grid-row: 3; border-left: 1px solid var(--raster); }
.block--daten { grid-column: 1 / span 8; padding-right: clamp(14px, 1.6vw, 24px); }
.block--person { grid-column: 9 / -1; }
}
.kenndaten { background: var(--papier); border: 1px solid var(--raster); border-top: 1px solid var(--text); }
.kenndaten th, .kenndaten td { padding: 9px 12px; border-bottom: 1px solid var(--raster); text-align: left; vertical-align: baseline; }
.kenndaten thead th { font-weight: 600; background: var(--kopfzelle); }
.kenndaten tbody th { font-weight: 400; hyphens: auto; }
.kenndaten td.wert { text-align: right; font-weight: 600; white-space: nowrap; }
.kenndaten td.einheit { color: var(--leise); white-space: nowrap; padding-left: 4px; }
.kenndaten td.bed { color: var(--leise); font-size: .875rem; font-stretch: 100%; }
.kenndaten tr:last-child th, .kenndaten tr:last-child td { border-bottom: 0; }
.kenndaten thead th.wert { text-align: right; }
/* Fachperson */
.person { display: grid; grid-template-columns: 64px minmax(0, 1fr); gap: 4px 16px; padding: 16px; background: var(--papier); border: 1px solid var(--raster); margin-top: 33px; }
.person__bild { grid-row: span 3; width: 64px; height: 64px; display: grid; place-items: center; background: var(--stahl); color: #fff; font: 600 1.25rem/1 'Strichpunkt Sans', sans-serif; font-stretch: 120%; }
.person b { font-size: 1.0625rem; }
.person p { font-size: .875rem; color: var(--leise); }
.person a { font-weight: 600; text-decoration: none; color: var(--signal); }
.person a:hover { text-decoration: underline; }
.person .zeit { grid-column: 1 / -1; border-top: 1px solid var(--raster); padding-top: 10px; margin-top: 8px; }
/* Downloads und Zubehör im Reiter */
.dateien { list-style: none; border-top: 1px solid var(--text); }
.dateien li { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px 18px; align-items: center; padding: 12px 0; border-bottom: 1px solid var(--raster); }
.dateien b { font-weight: 600; }
.dateien .meta { display: block; font-size: .8125rem; color: var(--leise); font-stretch: 87%; font-variant-numeric: tabular-nums; }
.dateien .btn { min-height: 44px; padding: 0 16px; font-size: .875rem; }
.sprachwahl { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 12px; font-size: .9375rem; }
.sprachwahl select { min-height: 44px; padding: 0 36px 0 12px; border: 1px solid rgba(17, 20, 23, .4); border-radius: 0; background: var(--papier) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23111417' stroke-width='1.5'/%3E%3C/svg%3E") right 12px center no-repeat; appearance: none; }
.hinweis { grid-column: 1 / -1; display: grid; gap: 4px; padding: 10px 12px; background: var(--warn-flaeche); border-left: 3px solid var(--warn); font-size: .875rem; }
.hinweis[hidden] { display: none; }
.cad-vorschau { grid-column: 1 / -1; display: grid; gap: 8px; padding: 12px; border: 1px dashed rgba(17, 20, 23, .4); font-size: .875rem; }
.cad-vorschau[hidden] { display: none; }
.cad-vorschau p { color: var(--leise); }
.zubehoer { background: var(--papier); border: 1px solid var(--raster); border-top: 1px solid var(--text); }
.zubehoer th, .zubehoer td { padding: 10px 12px; border-bottom: 1px solid var(--raster); text-align: left; vertical-align: middle; }
.zubehoer thead th { background: var(--kopfzelle); font-weight: 600; }
.zubehoer td:first-child { white-space: nowrap; font-weight: 600; letter-spacing: .02em; }
.zubehoer .kopieren { min-height: 36px; padding: 0 8px; margin-left: 4px; vertical-align: middle; }
.zubehoer .kopieren .ic-ok { left: 8px; }
.mitnehmen { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; cursor: pointer; white-space: nowrap; }
.mitnehmen input { width: 18px; height: 18px; accent-color: var(--signal); }
/* Anfrage-Dialog */
dialog { margin: auto; width: min(640px, calc(100% - 24px)); max-height: calc(100% - 24px); border: 0; padding: 0; background: var(--grund); color: var(--text); box-shadow: 0 24px 64px rgba(17, 20, 23, .28); }
dialog::backdrop { background: rgba(22, 32, 42, .55); }
.dlg__kopf { display: flex; justify-content: space-between; align-items: start; gap: 16px; padding: 20px 22px 14px; background: var(--stahl); color: #fff; }
.dlg__kopf h2 { font: 600 1.5rem/1.1 'Strichpunkt Sans', sans-serif; font-stretch: 112%; letter-spacing: -.02em; }
.dlg__kopf p { font-size: .875rem; color: rgba(255, 255, 255, .8); margin-top: 6px; }
.dlg__zu { width: 44px; height: 44px; flex: none; display: grid; place-items: center; color: #fff; margin: -6px -10px 0 0; }
.dlg__zu:hover { background: rgba(255, 255, 255, .1); }
.dlg__zu:focus-visible { outline-color: #fff; }
form.anfrage { padding: 18px 22px 22px; display: grid; gap: 14px; }
.feste { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 2px 14px; padding: 12px 14px; background: var(--papier); border: 1px solid var(--raster); border-left: 3px solid var(--signal); font-size: .9375rem; }
.feste dt { color: var(--leise); }
.feste dd { font-weight: 600; font-stretch: 87%; font-variant-numeric: tabular-nums; }
.felder { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 14px; }
.feld { display: grid; gap: 4px; min-width: 0; }
.feld--breit { grid-column: 1 / -1; }
.feld label { font-size: .875rem; font-weight: 600; }
.feld label span { font-weight: 400; color: var(--leise); }
.feld input, .feld textarea { min-height: 44px; padding: 8px 10px; border: 1px solid rgba(17, 20, 23, .45); border-radius: 0; background: var(--papier); width: 100%; }
.feld textarea { min-height: 72px; resize: vertical; }
.feld .fehler { font-size: .8125rem; color: #a3261b; display: none; }
.feld[data-fehler] input { border-color: #a3261b; box-shadow: inset 3px 0 0 #a3261b; }
.feld[data-fehler] .fehler { display: block; }
.klein { font-size: .8125rem; color: var(--leise); }
.dlg__fuss { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 18px; }
.bestaetigung { padding: 22px; display: grid; gap: 12px; }
.bestaetigung[hidden] { display: none; }
.bestaetigung h3 { font: 600 1.25rem/1.2 'Archivo', sans-serif; }
.bestaetigung dl { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 4px 14px; font-size: .9375rem; }
.bestaetigung dt { color: var(--leise); }
.bestaetigung dd { font-weight: 600; font-variant-numeric: tabular-nums; }
/* Status (Kopiert, Downloads) */
.ansage { position: fixed; left: 50%; bottom: 18px; transform: translate(-50%, calc(100% + 30px)); visibility: hidden; background: var(--stahl); color: #fff; padding: 10px 16px; font-size: .875rem; max-width: calc(100% - 24px); transition: transform var(--d1-aus) var(--ease-aus), visibility 0s linear var(--d1-aus); z-index: 20; }
.ansage[data-an] { transform: translate(-50%, 0); visibility: visible; transition: transform var(--d2) var(--ease); }
/* Pult der Demo */
/* Pult steht als schmale Leiste über der Seite (nicht fest), damit es nichts verdeckt */
.pult { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px; padding: 6px clamp(16px, 3vw, 40px); background: var(--papier); border-bottom: 1px solid var(--raster); font-size: .75rem; }
.pult > span { font-weight: 600; font-stretch: 75%; letter-spacing: .08em; text-transform: uppercase; color: var(--leise); }
.pult .gruppe { display: flex; flex-wrap: wrap; gap: 2px; }
.pult button { min-height: 32px; padding: 0 10px; font-size: .8125rem; color: var(--leise); box-shadow: inset 0 0 0 1px transparent; }
.pult button[aria-pressed=true] { background: var(--text); color: #fff; }
.pult button:not([aria-pressed=true]):hover { color: var(--text); box-shadow: inset 0 0 0 1px var(--raster); }
.pult .werte { color: var(--leise); font-stretch: 87%; font-weight: 400; letter-spacing: 0; text-transform: none; }
/* ── Varianten der Demo ──────────────────────────── */
[data-variante="einzel"] .groessen { display: block; }
[data-variante="einzel"] .nur-baureihe, [data-variante="konfiguriert"] .nur-baureihe { display: none; }
[data-variante="konfiguriert"] .konfig { display: grid; }
[data-variante="konfiguriert"] .lead { display: none; }
[data-variante="konfiguriert"] .nicht-konfig { display: none; }
[data-variante="einzel"] .unten, [data-variante="konfiguriert"] .unten { margin-top: 0; }
/* ── Druckansicht (gleiches Bild wie @media print) ─ */
.druckleiste { display: none; }
[data-druckansicht] .druckleiste { display: flex; position: sticky; top: 0; z-index: 25; align-items: center; flex-wrap: wrap; gap: 10px 16px; padding: 10px clamp(16px, 3vw, 40px); background: var(--stahl); color: #fff; font-size: .875rem; }
[data-druckansicht] .druckleiste .btn--kontur { color: #fff; box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .55); min-height: 44px; }
[data-druckansicht] .druckleiste .ecke { border-color: #fff; }
.druckkopf { display: none; }
[data-druckansicht] { background: #d9dbd7; }
[data-druckansicht] .blatt { background: #fff; max-width: 900px; margin: 24px auto; padding: 28px 32px; box-shadow: 0 2px 18px rgba(0, 0, 0, .12); }
[data-druckansicht] .kopf, [data-druckansicht] .achse, [data-druckansicht] .pfad, [data-druckansicht] .knoepfe, [data-druckansicht] .reiter, [data-druckansicht] .leiste .schalter, [data-druckansicht] .leiste .segment, [data-druckansicht] .leiste .btn, [data-druckansicht] .block--person, [data-druckansicht] .pult, [data-druckansicht] .groessen, [data-druckansicht] .konfig fieldset, [data-druckansicht] .extern, [data-druckansicht] .kopieren, [data-druckansicht] .blatt__innen::before { display: none !important; }
[data-druckansicht] .blatt__innen { border: 0; }
[data-druckansicht] .zone { border-top: 1px solid #111; }
[data-druckansicht] .zone::before, [data-druckansicht] .zone::after { display: none; }
[data-druckansicht] .druckkopf { display: flex; justify-content: space-between; gap: 16px; padding-bottom: 12px; font-size: .8125rem; }
[data-druckansicht] .intro, [data-druckansicht] .zeichnung { grid-column: 1 / -1; border: 0; }
[data-druckansicht] .block--daten { grid-column: 1 / -1; padding-right: 0; }
[data-druckansicht] [role=tabpanel] { display: block !important; }
[data-druckansicht] #panel-downloads, [data-druckansicht] #panel-zubehoer { display: none !important; }
[data-druckansicht] .rahmen { max-height: none; overflow: visible; }
[data-druckansicht] .varianten th, [data-druckansicht] .varianten td { white-space: normal; padding: 6px 8px; font-size: .8125rem; }
[data-druckansicht] .zeilenwahl input { display: none; }
[data-druckansicht] .zeilenwahl { display: block; }
[data-druckansicht] .zeilenwahl > span > span { display: block; margin-left: 0; }
[data-druckansicht] .lead { display: none; }
[data-druckansicht] .varianten th[scope=row], [data-druckansicht] .varianten thead th { position: static; }
@media print {
.kopf, .achse, .pfad, .knoepfe, .reiter, .leiste .schalter, .leiste .segment, .leiste .btn, .block--person, .pult, .druckleiste, .groessen, .konfig fieldset, .extern, .kopieren, .ansage, .blatt__innen::before, fundus-pult-knopf { display: none !important; }
html, body { background: #fff; }
.blatt { padding: 0; } .blatt__innen { border: 0; }
.zone::before, .zone::after { display: none; }
.druckkopf { display: flex; justify-content: space-between; font-size: 9pt; padding-bottom: 8pt; }
.intro, .zeichnung, .block--daten { grid-column: 1 / -1 !important; border: 0 !important; }
#panel-downloads, #panel-zubehoer { display: none !important; }
#panel-daten { display: block !important; }
.rahmen { max-height: none; overflow: visible; }
.varianten th[scope=row], .varianten thead th { position: static; }
}
/* ── Vorschau 960 × 720 ──────────────────────────── */
.vorschau body { overflow: hidden; }
.vorschau .pult { display: none; }
.vorschau .achse, .vorschau .pfad, .vorschau .lead, .vorschau .kopf nav, .vorschau .massliste, .vorschau .zeichnung figcaption { display: none; }
.vorschau .intro { gap: 12px; padding-bottom: 14px; }
.vorschau .zeichnung { padding-bottom: 12px; }
.vorschau h1 { font-size: 2.6rem; }
.vorschau [role=tabpanel] { padding-top: 12px; }
.vorschau .rahmen { max-height: 12.5rem; }
.vorschau [data-variante="konfiguriert"] .konfig legend { margin-bottom: 0; }
.vorschau .kurzwerte, .vorschau .knoepfe .btn--kontur, .vorschau h1 small { display: none; }
/* ── Telefon und schmal ──────────────────────────── */
@media (max-width: 980px) { .kopf nav { display: none; } }
@media (max-width: 640px) { .sprache .land { display: none; } }
@media (max-width: 640px) {
.kurzwerte dd { font-size: 1.125rem; }
.felder { grid-template-columns: minmax(0, 1fr); }
.dateien li { grid-template-columns: minmax(0, 1fr); }
.kenndaten td.bed { display: none; }
.kenndaten thead th.bed { display: none; }
.mass { font-size: .6875rem; }
.ansicht, .legende { display: none; }
.leiste h2 { width: 100%; }
}
.kenndaten .bed-mobil { display: none; }
@media (max-width: 640px) { .kenndaten .bed-mobil { display: block; font-size: .75rem; color: var(--leise); font-weight: 400; } }
@media (max-width: 360px) {
.kopf__rechts .sprache { display: none; }
.kenndaten th, .kenndaten td { padding: 8px 5px; font-size: .875rem; }
.kenndaten td.wert { white-space: normal; }
.blatt { padding-left: 10px; padding-right: 10px; }
[role=tabpanel] { padding-left: 10px; padding-right: 10px; }
h1 { font-size: 1.85rem; }
.kurzwerte { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.person { grid-template-columns: minmax(0, 1fr); }
.person__bild { grid-row: auto; }
}
@media (hover: none) {
.btn--kontur .ecke { width: 10px; height: 10px; }
.varianten tbody tr:hover td, .varianten tbody tr:hover th { background: inherit; }
.varianten tbody tr.an td, .varianten tbody tr.an th { background: var(--signal-hell); }
}
:root.reduziert *, :root.reduziert *::before, :root.reduziert *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; }
:root.reduziert .btn .kreisel { animation: none; border-right-color: currentColor; opacity: .6; }
@media (forced-colors: active) {
button, [role=button], [role=tab], [role=switch], input, select, textarea { border: 1px solid ButtonText !important; }
.varianten tbody tr.an th { outline: 2px solid Highlight; outline-offset: -2px; }
.reiter [role=tab][aria-selected=true] { outline: 2px solid Highlight; }
.wahl label:has(input:checked), .segment label:has(input:checked) { outline: 2px solid Highlight; }
}
</style>
</head>
<body>
<div class="pult" role="region" aria-label="Steuerung der Demo">
<span>Variante</span>
<div class="gruppe" role="group" aria-label="Variante der Demo">
<button type="button" data-v="baureihe" aria-pressed="true">Baureihe</button>
<button type="button" data-v="einzel" aria-pressed="false">Einzel</button>
<button type="button" data-v="konfiguriert" aria-pressed="false">Konfiguriert</button>
</div>
<span class="werte" data-werte></span>
</div>
<div class="druckleiste" role="region" aria-label="Druckansicht">
<span>Druckansicht: so sieht das Datenblatt auf Papier aus.</span>
<button class="btn btn--kontur" type="button" data-drucken><span class="ecke ecke--ol"></span><span class="ecke ecke--or"></span><span class="ecke ecke--ul"></span><span class="ecke ecke--ur"></span>Drucken</button>
<button class="btn btn--kontur" type="button" data-druck-zu><span class="ecke ecke--ol"></span><span class="ecke ecke--or"></span><span class="ecke ecke--ul"></span><span class="ecke ecke--ur"></span>Ansicht schließen</button>
</div>
<header class="kopf">
<div class="kopf__innen">
<a class="marke" href="#" aria-label="Vahlberg Vakuumtechnik, Startseite">
<svg viewBox="0 0 30 30" aria-hidden="true"><rect x=".75" y=".75" width="28.5" height="28.5" fill="none" stroke="#fff" stroke-width="1.5"/><path d="M7 8l8 15 8-15" fill="none" stroke="#fff" stroke-width="2.4"/><circle cx="15" cy="8" r="2" fill="#fff"/></svg>
<div><b>VAHLBERG</b><span>Vakuumtechnik · Olpe</span></div>
</a>
<nav aria-label="Hauptnavigation"><ul>
<li><a href="#" aria-current="page">Produkte</a></li><li><a href="#">Anwendungen</a></li><li><a href="#">Downloads</a></li><li><a href="#">Service</a></li><li><a href="#">Unternehmen</a></li>
</ul></nav>
<div class="kopf__rechts">
<a class="sprache" href="#">Deutsch<span class="land"> · Deutschland</span></a>
</div>
</div>
</header>
<div class="achse" aria-hidden="true"><ol><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li></ol></div>
<main class="blatt" id="inhalt">
<div class="blatt__innen">
<div class="druckkopf"><span><b>Vahlberg Vakuumtechnik GmbH</b> · Olpe</span><span>Datenblatt <span data-art-druck></span> · Stand 02.11.2026</span></div>
<nav class="zone pfad" aria-label="Brotkrumen">
<ol><li><a href="#">Produkte</a></li><li><a href="#">Vakuum-Greifsysteme</a></li><li><a href="#">Flächengreifer</a></li><li aria-current="page">Baureihe FGL</li></ol>
</nav>
<section class="zone" aria-labelledby="titel">
<p class="code abschnitt-code">01 · Baureihe FGL</p>
<div class="intro">
<h1 id="titel">Flächen­greifer FGL<small data-typzeile>Variante FGL-900 IO</small></h1>
<p class="lead">Vakuum-Flächengreifer mit eingebautem Ejektor für Blech, Holzwerkstoff und Kunststoffplatten. Ein Strömungsventil je Saugzelle hält das Teil auch, wenn nur ein Teil der Fläche belegt ist. Acht Längen von 300 bis 1800 mm.</p>
<div class="artikel">
<div class="artikel__text"><span class="code">Artikelnummer</span><b data-art>41.20.0900.21</b></div>
<button class="kopieren" type="button" data-kopieren aria-label="Artikelnummer kopieren">
<svg class="ic-kopie" viewBox="0 0 20 20" aria-hidden="true"><rect x="6.5" y="6.5" width="10" height="11" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M4 13.5V3.5h9" fill="none" stroke="currentColor" stroke-width="1.5"/></svg>
<svg class="ic-ok" viewBox="0 0 20 20" aria-hidden="true"><path d="M4 10.5l4 4 8-9" fill="none" stroke="currentColor" stroke-width="2" pathLength="1"/></svg>
<span data-kopieren-text>Kopieren</span>
</button>
</div>
<dl class="kurzwerte">
<div><dt>Haltekraft</dt><dd data-kurz="kraft">1140<span>N</span></dd></div>
<div><dt>Länge L</dt><dd data-kurz="laenge">900<span>mm</span></dd></div>
<div><dt>Gewicht</dt><dd data-kurz="gewicht">6,1<span>kg</span></dd></div>
</dl>
<div class="knoepfe">
<button class="btn btn--voll" type="button" data-anfragen>Diese Variante anfragen<svg class="pfeil" viewBox="0 0 16 16" aria-hidden="true"><path d="M1 8h13M9 3l5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></button>
<a class="btn btn--kontur nicht-konfig" href="#panel-downloads" data-zu-downloads><span class="ecke ecke--ol"></span><span class="ecke ecke--or"></span><span class="ecke ecke--ul"></span><span class="ecke ecke--ur"></span>Datenblatt <small data-db-groesse>PDF, 450 kB</small></a>
<button class="btn btn--kontur konfig-nur" type="button" data-extern hidden><span class="ecke ecke--ol"></span><span class="ecke ecke--or"></span><span class="ecke ecke--ul"></span><span class="ecke ecke--ur"></span>Im 3D-Konfigurator öffnen</button>
</div>
<div class="extern" data-extern-hinweis hidden>
<p><span class="markiert">Platzhalter</span> Der 3D-Konfigurator läuft bei einem externen Dienst. Erst mit dem nächsten Klick wird eine Verbindung aufgebaut; dabei gehen Ihre IP-Adresse und die Artikelnummer an den Anbieter. Datenschutzhinweis vom Kunden prüfen lassen.</p>
<p>Übergabe: <code data-extern-url></code></p>
<div class="knoepfe"><button class="btn btn--voll" type="button" data-extern-ok>Verbinden und öffnen</button><button class="btn btn--kontur" type="button" data-extern-ab><span class="ecke ecke--ol"></span><span class="ecke ecke--or"></span><span class="ecke ecke--ul"></span><span class="ecke ecke--ur"></span>Abbrechen</button></div>
</div>
<nav class="groessen" aria-label="Andere Längen der Baureihe">
<p class="code">Andere Längen dieser Baureihe</p>
<ul data-groessen></ul>
</nav>
</div>
<div class="konfig" role="group" aria-label="Merkmale wählen">
<fieldset>
<legend>Länge L in mm</legend>
<div class="wahl" data-wahl="laenge"></div>
</fieldset>
<fieldset>
<legend>Ansteuerung</legend>
<div class="wahl">
<label><input type="radio" name="k-steuer" value="pneu">pneumatisch</label>
<label><input type="radio" name="k-steuer" value="io" aria-describedby="grund-io">IO-Link</label>
</div>
<p class="grund" id="grund-io" data-grund-io></p>
</fieldset>
<fieldset>
<legend>Saugschaum</legend>
<div class="wahl">
<label><input type="radio" name="k-schaum" value="std">NBR bis 60 °C</label>
<label><input type="radio" name="k-schaum" value="ht">Silikon bis 80 °C</label>
</div>
</fieldset>
<fieldset>
<legend>Roboterflansch</legend>
<div class="wahl">
<label><input type="radio" name="k-flansch" value="0">ohne</label>
<label><input type="radio" name="k-flansch" value="1">ISO 9409-1-50-4-M6</label>
</div>
</fieldset>
</div>
<figure class="zeichnung" aria-labelledby="zeichnung-titel">
<div class="blattfeld">
<svg viewBox="0 0 1000 420" role="img" aria-labelledby="zeichnung-titel" aria-describedby="massliste" data-svg>
<defs>
<pattern id="schraffur" width="6" height="6" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><rect width="6" height="6" fill="#fbfbf9"/><line x1="0" y1="0" x2="0" y2="6" stroke="#111417" stroke-width="1"/></pattern>
<pattern id="schraffur-grob" width="10" height="10" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><rect width="10" height="10" fill="#fbfbf9"/><line x1="0" y1="0" x2="0" y2="10" stroke="#111417" stroke-width="1"/></pattern>
</defs>
<g data-geo></g>
<line class="trenn" x1="24" y1="252" x2="976" y2="252"/>
<!-- Ansicht A: Stirnseite, doppelt so groß -->
<g>
<rect class="flaeche" x="100" y="274" width="40" height="30"/>
<rect class="kante" data-ejektor-io x="106" y="265" width="12" height="9"/>
<rect class="kante" x="60" y="304" width="120" height="71"/>
<rect class="schaum" x="60" y="375" width="120" height="15"/>
<line class="mittel" x1="120" y1="262" x2="120" y2="400"/>
<line class="duenn" x1="184" y1="304" x2="206" y2="304"/><line class="duenn" x1="184" y1="390" x2="206" y2="390"/><line class="duenn" x1="200" y1="304" x2="200" y2="390"/>
<path class="pfeilspitze" d="M200 304l-3.2 10h6.4z"/><path class="pfeilspitze" d="M200 390l-3.2 -10h6.4z"/>
<line class="duenn" x1="60" y1="394" x2="60" y2="412"/><line class="duenn" x1="180" y1="394" x2="180" y2="412"/><line class="duenn" x1="60" y1="406" x2="180" y2="406"/>
<path class="pfeilspitze" d="M60 406l10 -3.2v6.4z"/><path class="pfeilspitze" d="M180 406l-10 -3.2v6.4z"/>
</g>
<!-- Detail Z: Schnitt durch drei Saugzellen, vergrößert -->
<g transform="translate(330 0)">
<rect class="kante" x="0" y="300" width="300" height="48"/>
<g class="zelle"><rect class="kammer" x="34" y="306" width="32" height="36"/><circle class="kugel" cx="50" cy="318" r="9"/><line class="kante" x1="50" y1="342" x2="50" y2="348"/></g>
<g class="zelle"><rect class="kammer" x="134" y="306" width="32" height="36"/><circle class="kugel" cx="150" cy="318" r="9"/><line class="kante" x1="150" y1="342" x2="150" y2="348"/></g>
<g class="zelle"><rect class="kammer" x="234" y="306" width="32" height="36"/><circle class="kugel" cx="250" cy="318" r="9"/><line class="kante" x1="250" y1="342" x2="250" y2="348"/></g>
<path class="schaum-grob" d="M0 348h22v60h-22z M78 348h44v60h-44z M178 348h44v60h-44z M278 348h22v60h-22z"/>
<line class="mittel" x1="50" y1="292" x2="50" y2="414"/><line class="mittel" x1="150" y1="292" x2="150" y2="414"/><line class="mittel" x1="250" y1="292" x2="250" y2="414"/>
<line class="duenn" x1="50" y1="282" x2="150" y2="282"/><path class="pfeilspitze" d="M50 282l10 -3.2v6.4z"/><path class="pfeilspitze" d="M150 282l-10 -3.2v6.4z"/>
</g>
</svg>
<span class="ansicht" style="left:6%;top:54.5%;max-width:60%">Seitenansicht · Draufsicht</span>
<span class="ansicht" style="left:22%;top:64%">Ansicht A</span>
<span class="ansicht" style="left:66%;top:70%">Detail Z · Saugzelle mit Strömungsventil</span>
<span class="legende" data-max>max. 1800</span>
<span class="mass" data-mass="l">900</span>
<span class="mass mass--senk" data-mass="h">86</span>
<span class="mass mass--senk" data-mass="b">120</span>
<span class="mass mass--senk" data-mass="h2" style="left:20%;top:82.4%">86</span>
<span class="mass" data-mass="b2" style="left:12%;top:96.2%">120</span>
<span class="mass" style="left:43%;top:66.4%" data-mass-raster>25</span>
</div>
<figcaption id="zeichnung-titel">Maßzeichnung <span data-typ>FGL-900 IO</span>: Seitenansicht und Draufsicht, Stirnseite (Ansicht A, 2 : 1) und Saugzelle im Schnitt (Detail Z, 8 : 1). Maße in <span data-einheit-wort>mm</span>.</figcaption>
<ul class="massliste" id="massliste" aria-label="Maße als Liste" data-massliste></ul>
</figure>
</section>
<section class="zone" aria-labelledby="daten-titel">
<p class="code abschnitt-code" id="daten-titel">02 · Technische Daten, Downloads, Zubehör</p>
<div>
<div class="reiter" role="tablist" aria-label="Produktinformationen">
<button role="tab" id="tab-daten" aria-controls="panel-daten" aria-selected="true" tabindex="0">Technische Daten</button>
<button role="tab" id="tab-downloads" aria-controls="panel-downloads" aria-selected="false" tabindex="-1">Downloads<span data-anzahl-dl>5</span></button>
<button role="tab" id="tab-zubehoer" aria-controls="panel-zubehoer" aria-selected="false" tabindex="-1">Zubehör<span data-anzahl-zb>5</span></button>
</div>
<div role="tabpanel" id="panel-daten" aria-labelledby="tab-daten" tabindex="0">
<div class="leiste">
<h2 class="nur-baureihe">Varianten vergleichen</h2>
<h2 class="nicht-baureihe" hidden>Kenndaten</h2>
<button class="schalter nur-baureihe" type="button" role="switch" aria-checked="false" data-unterschiede><span class="schalter__spur" aria-hidden="true"></span>Nur Unterschiede</button>
<fieldset class="segment">
<legend>Einheiten</legend>
<label><input type="radio" name="einheit" value="metrisch" checked>metrisch</label>
<label><input type="radio" name="einheit" value="imperial">Zoll, lbf</label>
</fieldset>
<button class="btn btn--kontur" type="button" data-druck><span class="ecke ecke--ol"></span><span class="ecke ecke--or"></span><span class="ecke ecke--ul"></span><span class="ecke ecke--ur"></span>Druckansicht</button>
</div>
<div class="rahmen nur-baureihe" tabindex="0" role="region" aria-labelledby="cap-varianten">
<table class="varianten">
<caption id="cap-varianten" class="sr">Varianten der Baureihe FGL. Erste Spalte: Artikelnummer zum Auswählen. Tabelle scrollt waagerecht.</caption>
<thead><tr data-kopfzeile></tr></thead>
<tbody data-zeilen></tbody>
</table>
</div>
<p class="gleich nur-baureihe" data-gleich hidden></p>
<div class="unten">
<div class="block block--daten">
<h2 class="nur-baureihe" style="margin-top:4px">Kenndaten <span data-typ>FGL-900 IO</span></h2>
<p class="unter">Werte nach Werksprüfung. Prüfbedingung je Zeile; leere Merkmale entfallen.</p>
<table class="kenndaten">
<caption class="sr">Kenndaten der gewählten Variante mit Wert, Einheit und Prüfbedingung</caption>
<thead><tr><th scope="col">Merkmal</th><th scope="col" class="wert">Wert</th><th scope="col">Einheit</th><th scope="col" class="bed">Prüfbedingung</th></tr></thead>
<tbody data-kenndaten></tbody>
</table>
</div>
<aside class="block block--person" aria-label="Ihre Fachperson">
<div class="person">
<div class="person__bild" aria-hidden="true">JR</div>
<b>Jonas Ritterbach</b>
<p>Anwendungstechnik Flächengreifer · Deutsch, Englisch, Polnisch</p>
<p><a href="#">02761 9418-214</a> · <a href="#">j.ritterbach@vahlberg.example</a></p>
<p class="zeit">Antwort auf Anfragen werktags binnen 24 Stunden. Versuche mit Ihren Teilen im Technikum Olpe nach Absprache.</p>
</div>
</aside>
</div>
</div>
<div role="tabpanel" id="panel-downloads" aria-labelledby="tab-downloads" tabindex="0" hidden>
<div class="sprachwahl">
<label for="dl-sprache">Sprache der Dokumente</label>
<select id="dl-sprache" data-dl-sprache>
<option value="de">Deutsch</option><option value="en">English</option><option value="fr">Français</option><option value="pl">Polski</option><option value="cs">Čeština</option><option value="it">Italiano</option>
</select>
</div>
<ul class="dateien" data-dateien></ul>
</div>
<div role="tabpanel" id="panel-zubehoer" aria-labelledby="tab-zubehoer" tabindex="0" hidden>
<div class="rahmen" style="max-height:none" tabindex="0" role="region" aria-labelledby="cap-zub">
<table class="zubehoer">
<caption id="cap-zub" class="sr">Zubehör und Ersatzteile passend zur gewählten Variante</caption>
<thead><tr><th scope="col">Artikelnummer</th><th scope="col">Bezeichnung</th><th scope="col">Hinweis</th><th scope="col">Anfrage</th></tr></thead>
<tbody data-zubehoer></tbody>
</table>
</div>
</div>
</div>
</section>
</div>
</main>
<dialog data-dialog aria-labelledby="dlg-titel">
<div class="dlg__kopf">
<div><h2 id="dlg-titel">Diese Variante anfragen</h2><p>Jonas Ritterbach, Anwendungstechnik, antwortet werktags binnen 24 Stunden.</p></div>
<button class="dlg__zu" type="button" data-dlg-zu aria-label="Anfrage schließen"><svg width="18" height="18" viewBox="0 0 18 18" aria-hidden="true"><path d="M2 2l14 14M16 2L2 16" stroke="currentColor" stroke-width="1.8"/></svg></button>
</div>
<form class="anfrage" novalidate data-form>
<dl class="feste">
<dt>Artikel</dt><dd data-dlg-art>41.20.0900.21</dd>
<dt>Typ</dt><dd data-dlg-typ>FGL-900 IO</dd>
<dt>Zubehör</dt><dd data-dlg-zub>keins gewählt</dd>
</dl>
<div class="felder">
<div class="feld"><label for="f-menge">Stückzahl</label><input id="f-menge" name="menge" type="number" inputmode="numeric" min="1" value="2"></div>
<div class="feld"><label for="f-teil">Werkstück <span>(optional)</span></label><input id="f-teil" name="teil" type="text" placeholder="z. B. Blech 1,5 mm, 800 × 600 mm"></div>
<div class="feld"><label for="f-name">Name</label><input id="f-name" name="name" type="text" autocomplete="name" required aria-describedby="e-name"><span class="fehler" id="e-name">Bitte Ihren Namen angeben.</span></div>
<div class="feld"><label for="f-mail">E-Mail</label><input id="f-mail" name="mail" type="email" autocomplete="email" required aria-describedby="e-mail"><span class="fehler" id="e-mail">Bitte eine E-Mail-Adresse mit @ angeben.</span></div>
<div class="feld feld--breit"><label for="f-datei">Zeichnung <span>(optional, STEP, DXF oder PDF, bis 20 MB)</span></label><input id="f-datei" name="datei" type="file" accept=".step,.stp,.dxf,.pdf"></div>
</div>
<p class="klein"><span class="markiert">Platzhalter</span> Ihre Daten und Zeichnungen behandeln wir vertraulich; eine Geheimhaltungsvereinbarung ist vorab möglich. Hinweise zum Datenschutz: Text vom Kunden prüfen lassen.</p>
<div class="dlg__fuss">
<button class="btn btn--voll" type="submit" data-senden><span class="kreisel" aria-hidden="true"></span><span data-senden-text>Anfrage senden</span><svg class="pfeil" viewBox="0 0 16 16" aria-hidden="true"><path d="M1 8h13M9 3l5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></button>
<span class="klein">Kein Konto nötig.</span>
</div>
</form>
<div class="bestaetigung" data-ok hidden tabindex="-1">
<h3>Anfrage ist angekommen</h3>
<dl><dt>Vorgang</dt><dd data-vorgang>A-261102-0417</dd><dt>Artikel</dt><dd data-ok-art></dd><dt>Zuständig</dt><dd>Jonas Ritterbach, 02761 9418-214</dd><dt>Antwort</dt><dd>bis morgen, 12:00 Uhr</dd></dl>
<p class="klein">Eine Kopie geht an Ihre E-Mail-Adresse. (Demo: es wird nichts gesendet.)</p>
<div><button class="btn btn--kontur" type="button" data-dlg-zu><span class="ecke ecke--ol"></span><span class="ecke ecke--or"></span><span class="ecke ecke--ul"></span><span class="ecke ecke--ur"></span>Schließen</button></div>
</div>
</dialog>
<p class="sr" role="status" data-status></p>
<p class="ansage" aria-hidden="true" data-ansage></p>
<script>
(() => {
const html = document.documentElement;
const reduziert = () => html.classList.contains('reduziert');
const $ = (s, r = document) => r.querySelector(s);
const $$ = (s, r = document) => [...r.querySelectorAll(s)];
const SPRACHE = 'de-DE';
const fmtCache = {};
const zahl = (n, d = 0) => (fmtCache[d] ||= new Intl.NumberFormat(SPRACHE, { minimumFractionDigits: d, maximumFractionDigits: d, useGrouping: 'min2' })).format(n);
const minus = '−';
const vz = (n, d) => (n < 0 ? minus : '') + zahl(Math.abs(n), d);
/* ── Daten: eine Quelle für Tabelle, Kenndaten, Zeichnung und JSON-LD ── */
const LAENGEN = [300, 450, 600, 750, 900, 1200, 1500, 1800];
function variante({ L, steuer, schaum = 'std', flansch = false }) {
const io = steuer === 'io', ht = schaum === 'ht';
const ej = Math.ceil(L / 450), zellen = L / 25;
return {
L, io, ht, flansch, ej, zellen,
art: `41.20.${String(L).padStart(4, '0')}.${io ? 2 : 0}${ht ? 3 : 1}${flansch ? '-F' : ''}`,
typ: `FGL-${L}${io ? ' IO' : ''}${ht ? ' HT' : ''}${flansch ? ' F' : ''}`,
laenge: L, breite: 120, hoehe: 86,
kraft: Math.round(zellen * 31.667),
volumen: 150 * ej, luft: 95 * ej, vakuum: -780, druck: [4, 6],
schall: [0, 68, 71, 72, 73][ej],
gewicht: Math.round((1.9 + L * 0.0046 + (io ? 0.1 : 0) + (flansch ? 0.4 : 0)) * 10) / 10,
temp: ht ? [0, 80] : [0, 60],
anschluss: L <= 450 ? 'G 1/4' : L <= 900 ? 'G 3/8' : 'G 1/2',
schutz: io ? 'IP65' : '',
iolink: io ? 'Port Class A' : '',
schaumtext: ht ? 'Silikon, 15 mm' : 'NBR, 15 mm',
flanschtext: flansch ? 'ISO 9409-1-50-4-M6' : '',
};
}
const BAUREIHE = LAENGEN.map(L => variante({ L, steuer: L >= 900 ? 'io' : 'pneu' }));
/* Merkmale: metrisch gespeichert, Umrechnung erst bei der Ausgabe */
const M = {
laenge: { name: 'Länge L', ein: 'mm', d: 0, imp: ['in', v => v / 25.4, 1], unit: 'MMT' },
breite: { name: 'Breite B', ein: 'mm', d: 0, imp: ['in', v => v / 25.4, 1], unit: 'MMT' },
hoehe: { name: 'Höhe H', ein: 'mm', d: 0, imp: ['in', v => v / 25.4, 1], unit: 'MMT' },
zellen: { name: 'Saugzellen', ein: 'Stück', d: 0, bed: 'Raster 25 mm, Strömungsventil je Zelle' },
kraft: { name: 'Haltekraft', ein: 'N', d: 0, imp: ['lbf', v => v * 0.224809, 0], bed: 'bei ' + minus + '600 mbar, Stahlblech DC01 geölt, senkrecht zur Saugfläche', unit: 'NEW' },
volumen: { name: 'Saugvolumen', ein: 'l/min', d: 0, imp: ['cfm', v => v * 0.0353147, 1], bed: 'frei saugend, Betriebsdruck 5 bar' },
vakuum: { name: 'Max. Unterdruck', ein: 'mbar', d: 0, imp: ['inHg', v => v * 0.02953, 1], bed: 'bei Betriebsdruck 5 bar' },
luft: { name: 'Druckluftverbrauch', ein: 'Nl/min', d: 0, imp: ['scfm', v => v * 0.0353147, 1], bed: 'bei 5 bar, Ejektor ein' },
druck: { name: 'Betriebsdruck', ein: 'bar', d: 0, imp: ['psi', v => v * 14.5038, 0], bed: 'ölfreie Druckluft, ISO 8573-1 Klasse 7:4:4' },
schall: { name: 'Schalldruckpegel', ein: 'dB(A)', d: 0, bed: 'in 1 m Abstand, frei saugend' },
gewicht: { name: 'Gewicht', ein: 'kg', d: 1, imp: ['lb', v => v * 2.20462, 1], bed: 'ohne Schläuche und Kabel', unit: 'KGM' },
temp: { name: 'Betriebstemperatur', ein: '°C', d: 0, imp: ['°F', v => v * 9 / 5 + 32, 0] },
anschluss: { name: 'Anschluss Druckluft', text: true, bed: 'Innengewinde' },
schaumtext: { name: 'Saugschaum', text: true, bed: 'tauschbar, Ersatzplatte im Zubehör' },
flanschtext: { name: 'Roboterflansch', text: true, bed: 'montiert, Lochkreis 50 mm' },
schutz: { name: 'Schutzart', text: true, bed: 'nach DIN EN 60529' },
iolink: { name: 'IO-Link', text: true, bed: 'Vakuum, Zustand, Zykluszähler' },
};
const SPALTEN = ['laenge', 'kraft', 'volumen', 'luft', 'gewicht', 'schall', 'anschluss', 'iolink', 'breite', 'hoehe', 'vakuum', 'druck'];
const KENN = ['kraft', 'volumen', 'vakuum', 'luft', 'druck', 'zellen', 'schall', 'laenge', 'breite', 'hoehe', 'gewicht', 'temp', 'anschluss', 'schaumtext', 'flanschtext', 'schutz', 'iolink'];
let imperial = false, nurUnterschiede = false;
function wert(k, v) { /* → { w, e } formatiert; leer → null */
const m = M[k], x = v[k];
if (x === '' || x == null) return null;
if (m.text) return { w: x, e: '' };
const [e, f, d] = imperial && m.imp ? m.imp : [m.ein, n => n, m.d];
if (Array.isArray(x)) return { w: `${vz(f(x[0]), d)} bis ${vz(f(x[1]), d)}`, e };
return { w: vz(f(x), d), e };
}
const roh = (k, v) => JSON.stringify(v[k]);
/* ── Zustand ── */
const q = new URLSearchParams(location.search);
const VARIANTEN = ['baureihe', 'einzel', 'konfiguriert'];
let modus = VARIANTEN.includes(q.get('v')) ? q.get('v') : 'baureihe';
let gewaehlt = BAUREIHE[4];
let konf = { L: 900, steuer: 'io', schaum: 'std', flansch: false };
const zubehoerWahl = new Set();
/* ── Variantentabelle ── */
function baueKopf() {
const tr = $('[data-kopfzeile]');
tr.innerHTML = '<th scope="col">Artikelnummer<span class="einheit">Typ</span></th>' + SPALTEN.map(k => {
const m = M[k], e = m.text ? ' ' : (imperial && m.imp ? m.imp[0] : m.ein);
return `<th scope="col" data-spalte="${k}"${m.text ? ' class="text"' : ''}>${m.name}<span class="einheit">${e}</span></th>`;
}).join('');
}
function baueZeilen() {
$('[data-zeilen]').innerHTML = BAUREIHE.map((v, i) => `<tr data-i="${i}"><th scope="row"><label class="zeilenwahl"><input type="radio" name="variante" value="${i}"><span><b>${v.art}</b> <span>${v.typ}</span></span></label></th>` +
SPALTEN.map(k => { const w = wert(k, v); return `<td data-spalte="${k}"${M[k].text ? ' class="text"' : ''}>${w ? w.w : '<span class="leer" aria-hidden="true">–</span><span class="sr">ohne</span>'}</td>`; }).join('') + '</tr>').join('');
markiereZeile();
spaltenFiltern();
}
function markiereZeile() {
$$('[data-zeilen] tr').forEach(tr => {
const an = BAUREIHE[+tr.dataset.i] === gewaehlt;
tr.classList.toggle('an', an);
tr.querySelector('input').checked = an;
if (an) sichtbarImRahmen(tr);
});
}
/* Gewählte Zeile im Tabellenrahmen sichtbar halten (nur der Rahmen scrollt, nicht die Seite) */
function sichtbarImRahmen(tr) {
const r = tr.closest('.rahmen'); if (!r || !r.clientHeight) return;
const kopf = r.querySelector('thead').offsetHeight, oben = tr.offsetTop, unten = oben + tr.offsetHeight;
if (oben - kopf < r.scrollTop) r.scrollTop = oben - kopf;
else if (unten > r.scrollTop + r.clientHeight) r.scrollTop = unten - r.clientHeight;
}
function spaltenFiltern() {
const aus = nurUnterschiede || document.body.hasAttribute('data-druckansicht'), gleiche = [];
SPALTEN.forEach(k => {
const gleich = BAUREIHE.every(v => roh(k, v) === roh(k, BAUREIHE[0]));
if (gleich) gleiche.push(k);
$$(`.varianten [data-spalte="${k}"]`).forEach(c => c.classList.toggle('aus', aus && gleich));
});
/* Ausgeblendete Spalten nicht verschweigen: gleiche Werte einmal nennen */
const g = $('[data-gleich]');
g.hidden = !aus || !gleiche.length;
g.innerHTML = 'Für alle Varianten gleich: ' + gleiche.map(k => { const w = wert(k, BAUREIHE[0]); return `<b>${M[k].name}</b> ${w.w}${w.e ? ' ' + w.e : ''}`; }).join(' · ');
}
/* ── Kenndaten: leere Werte erzeugen keine Zeile ── */
function baueKenndaten(v) {
$('[data-kenndaten]').innerHTML = KENN.map(k => {
const w = wert(k, v); if (!w) return '';
const b = M[k].bed || '';
return `<tr><th scope="row">${M[k].name}${b ? `<span class="bed-mobil">${b}</span>` : ''}</th><td class="wert">${w.w}</td><td class="einheit">${w.e}</td><td class="bed">${b}</td></tr>`;
}).join('');
}
/* ── Maßzeichnung: nur die Länge bewegt sich, waagerecht ── */
const S = 0.47, X0 = 46, VB_B = 1000, VB_H = 420;
let zeichnungL = 900, anim = 0;
const pfeilH = (x, y, r) => `<path class="pfeilspitze" d="M${x} ${y}l${r * 10} -3.2v6.4z"/>`;
const pfeilV = (x, y, r) => `<path class="pfeilspitze" d="M${x} ${y}l-3.2 ${r * 10}h6.4z"/>`;
function massV(x, y1, y2, xa) { return `<line class="duenn" x1="${xa}" y1="${y1}" x2="${x + 6}" y2="${y1}"/><line class="duenn" x1="${xa}" y1="${y2}" x2="${x + 6}" y2="${y2}"/><line class="duenn" x1="${x}" y1="${y1}" x2="${x}" y2="${y2}"/>${pfeilV(x, y1, 1)}${pfeilV(x, y2, -1)}`; }
function massW(y, x1, x2, ya) { return `<line class="duenn" x1="${x1}" y1="${y - 6}" x2="${x1}" y2="${ya}"/><line class="duenn" x1="${x2}" y1="${y - 6}" x2="${x2}" y2="${ya}"/><line class="duenn" x1="${x1}" y1="${y}" x2="${x2}" y2="${y}"/>${pfeilH(x1, y, 1)}${pfeilH(x2, y, -1)}`; }
function geo(L, v) {
const w = L * S, H = 86 * S, B = 120 * S, yL = 26, yT = 70, yD = 160, ejB = 96 * S, ejH = 30 * S, sch = 15 * S;
const ej = Math.max(1, Math.ceil(L / 450));
let s = '';
// Seitenansicht; gestrichelt bis zur längsten Variante der Baureihe
if (L < 1799) s += `<line class="geist" x1="${X0 + w + 12}" y1="${yL}" x2="${X0 + 1800 * S}" y2="${yL}"/><line class="duenn" x1="${X0 + 1800 * S}" y1="${yL - 5}" x2="${X0 + 1800 * S}" y2="${yL + 5}"/>`;
for (let i = 0; i < ej; i++) {
const cx = X0 + w * (i + .5) / ej;
s += `<rect class="flaeche" x="${cx - ejB / 2}" y="${yT - ejH}" width="${ejB}" height="${ejH}"/>`;
if (v.io && i === 0) s += `<rect class="kante" x="${cx - ejB / 2 + 6}" y="${yT - ejH - 9}" width="12" height="9"/>`;
}
if (v.flansch) { const fy = ej % 2 ? yT - ejH - 9 : yT - 9; s += `<rect class="kante" x="${X0 + w / 2 - 30}" y="${fy}" width="60" height="9"/><line class="mittel" x1="${X0 + w / 2}" y1="${fy - 10}" x2="${X0 + w / 2}" y2="${yT + H + 8}"/>`; }
s += `<rect class="kante" x="${X0}" y="${yT}" width="${w}" height="${H - sch}"/><rect class="schaum" x="${X0}" y="${yT + H - sch}" width="${w}" height="${sch}"/>`;
s += `<rect class="kante" x="${X0 - 9}" y="${yT + 8}" width="9" height="12"/>`;
s += massW(yL, X0, X0 + w, yT - ejH - 3);
const xH = X0 + w + 24;
s += massV(xH, yT, yT + H, X0 + w + 4);
// Draufsicht mit Saugzellen im Raster 25 mm
s += `<rect class="kante" x="${X0}" y="${yD}" width="${w}" height="${B}"/>`;
const n = Math.floor(L / 25 + 1e-6);
for (let j = 0; j < n; j++) for (let r = 0; r < 3; r++) s += `<circle class="loch" cx="${X0 + (j + .5) * 25 * S}" cy="${yD + B * (r + 1) / 4}" r="2.6"/>`;
s += `<line class="mittel" x1="${X0 - 16}" y1="${yD + B / 2}" x2="${X0 + w + 16}" y2="${yD + B / 2}"/>`;
s += massV(xH, yD, yD + B, X0 + w + 4);
$('[data-geo]').innerHTML = s;
// Beschriftung als HTML (lesbar auf dem Telefon), Lage in Prozent der viewBox
const p = (x, y, el) => { el.style.left = (x / VB_B * 100) + '%'; el.style.top = (y / VB_H * 100) + '%'; };
p(X0 + w / 2, yL - 2, $('[data-mass="l"]'));
p(xH, yT + H / 2, $('[data-mass="h"]'));
p(xH, yD + B / 2, $('[data-mass="b"]'));
$('[data-ejektor-io]').style.display = v.io ? '' : 'none';
$('[data-max]').style.visibility = L < 1799 ? '' : 'hidden';
p(X0 + 1800 * S - 40, yL - 8, $('[data-max]'));
}
function zeichne(v, sanft) {
const ziel = v.L, start = zeichnungL;
const mass = (k, x) => wert(k, { [k]: x }).w;
const setzeText = () => {
$('[data-mass="l"]').textContent = mass('laenge', ziel);
$$('[data-mass="h"], [data-mass="h2"]').forEach(e => e.textContent = mass('hoehe', v.hoehe));
$$('[data-mass="b"], [data-mass="b2"]').forEach(e => e.textContent = mass('breite', v.breite));
};
cancelAnimationFrame(anim);
if (!sanft || reduziert() || Math.abs(start - ziel) < 1) { zeichnungL = ziel; geo(ziel, v); setzeText(); return; }
const t0 = performance.now(), dauer = 350;
const ease = t => 1 - Math.pow(1 - t, 3); // power3.out = cubic-bezier(0.25, 1, 0.5, 1)
setzeText();
const schritt = now => {
const t = Math.min(1, (now - t0) / dauer);
zeichnungL = start + (ziel - start) * ease(t);
geo(zeichnungL, v);
if (t < 1) anim = requestAnimationFrame(schritt);
};
anim = requestAnimationFrame(schritt);
}
/* ── Downloads und Zubehör der gewählten Variante ── */
const SPRACHNAME = { de: 'Deutsch', en: 'Englisch', fr: 'Französisch', pl: 'Polnisch', cs: 'Tschechisch', it: 'Italienisch' };
function datenblattSprachen(v) { const s = ['de', 'en', 'fr']; if (v.L <= 900 && !v.ht) s.push('pl'); if (v.L <= 600 && !v.ht) s.push('cs'); return s; }
function kb(n) { return n >= 1000 ? zahl(n / 1000, 1) + ' MB' : zahl(n) + ' kB'; }
function dateien(v, sprache) {
const hat = datenblattSprachen(v), dbS = hat.includes(sprache) ? sprache : 'en';
const dbGroesse = 360 + Math.round(v.L / 10);
const liste = [
{ titel: `Datenblatt ${v.art}`, meta: `${SPRACHNAME[dbS]} · Ausgabe 09/2026 · PDF · ${kb(dbGroesse)}`, knopf: `PDF, ${kb(dbGroesse)}`, fehlt: !hat.includes(sprache) ? sprache : null, hat },
{ titel: 'Betriebsanleitung Baureihe FGL', meta: `${SPRACHNAME[sprache]} · Version 3.2 · Stand 04/2026 · PDF · 4,8 MB · auch als HTML-Fassung`, knopf: 'PDF, 4,8 MB' },
{ titel: 'EU-Einbauerklärung Baureihe FGL', meta: `${SPRACHNAME[sprache === 'de' ? 'de' : 'en']} · Stand 04/2026 · PDF · 210 kB`, knopf: 'PDF, 210 kB', recht: true },
{ titel: `CAD 3D ${v.typ}`, meta: `STEP AP214 · Stand 09/2026 · ${kb(1400 + v.L)}`, knopf: `STEP, ${kb(1400 + v.L)}`, cad: true },
{ titel: `CAD 2D ${v.typ}`, meta: `DXF, Seitenansicht und Draufsicht · Stand 09/2026 · 180 kB`, knopf: 'DXF, 180 kB' },
];
if (v.io) liste.push({ titel: 'IODD Gerätebeschreibung FGL IO', meta: 'IO-Link 1.1 · Version 1.4 · ZIP · 96 kB', knopf: 'ZIP, 96 kB' });
return liste;
}
const ecken = '<span class="ecke ecke--ol"></span><span class="ecke ecke--or"></span><span class="ecke ecke--ul"></span><span class="ecke ecke--ur"></span>';
function baueDownloads(v) {
const sprache = $('[data-dl-sprache]').value, liste = dateien(v, sprache);
$('[data-anzahl-dl]').textContent = liste.length;
$('[data-dateien]').innerHTML = liste.map((d, i) => `<li><div><b>${d.titel}</b>${d.recht ? ' <span class="markiert">vom Kunden prüfen lassen</span>' : ''}<span class="meta">${d.meta}</span></div>` +
`<button class="btn btn--kontur" type="button" data-laden="${i}" aria-label="${d.titel} herunterladen, ${d.knopf}">${ecken}<span class="kreisel" aria-hidden="true"></span><svg class="pfeil" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 1v10M3.5 6.5L8 11l4.5-4.5M2 15h12" fill="none" stroke="currentColor" stroke-width="1.6"/></svg><span data-t>${d.knopf}</span></button>` +
(d.fehlt ? `<p class="hinweis" role="note"><b>Dieses Datenblatt gibt es noch nicht auf ${SPRACHNAME[d.fehlt]}.</b><span>Angeboten wird die englische Fassung. Vorhanden: ${d.hat.map(s => SPRACHNAME[s]).join(', ')}.</span></p>` : '') +
(d.cad ? `<div><button class="btn btn--kontur" type="button" data-cad style="min-height:44px">${ecken}3D-Vorschau laden</button></div><div class="cad-vorschau" data-cad-feld hidden><p><span class="markiert">Platzhalter</span> Die 3D-Vorschau kommt von einem externen CAD-Dienst. Erst nach Ihrem Klick wird verbunden; dabei gehen IP-Adresse und Artikelnummer an den Anbieter. Datenschutzhinweis vom Kunden prüfen lassen.</p><div><button class="btn btn--voll" type="button" data-cad-ok>Verbinden</button></div></div>` : '') + '</li>').join('');
$('[data-db-groesse]').textContent = `PDF, ${kb(360 + Math.round(v.L / 10))}`;
}
function zubehoer(v) {
const z = [
{ art: '41.90.0020.00', name: 'Schnellwechselflansch SWF-50', hin: 'Lochbild ISO 9409-1-50-4-M6', nicht: v.flansch },
{ art: `41.90.0110.${String(v.L / 10).padStart(3, '0')}`, name: `Ersatz-Saugschaum ${v.ht ? 'Silikon' : 'NBR'} ${v.L} × 120 mm`, hin: 'selbstklebend, mit Lochmaske' },
{ art: '41.90.0310.00', name: 'Schalldämpfer SD-38', hin: `${v.ej} Stück je Greifer` },
{ art: '41.90.0420.05', name: 'Anschlussleitung M12, 5-polig, 5 m', hin: 'für IO-Link-Port Class A', nicht: !v.io },
{ art: '41.90.0505.00', name: 'Filtereinsatz 60 µm', hin: 'Wechsel nach Sichtprüfung, spätestens jährlich' },
].filter(x => !x.nicht);
$('[data-anzahl-zb]').textContent = z.length;
$('[data-zubehoer]').innerHTML = z.map(x => `<tr><td>${x.art}<button class="kopieren" type="button" data-kopieren-zub="${x.art}" aria-label="Artikelnummer ${x.art} kopieren"><svg class="ic-kopie" viewBox="0 0 20 20" aria-hidden="true"><rect x="6.5" y="6.5" width="10" height="11" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M4 13.5V3.5h9" fill="none" stroke="currentColor" stroke-width="1.5"/></svg><svg class="ic-ok" viewBox="0 0 20 20" aria-hidden="true"><path d="M4 10.5l4 4 8-9" fill="none" stroke="currentColor" stroke-width="2" pathLength="1"/></svg></button></td><td>${x.name}</td><td>${x.hin}</td><td><label class="mitnehmen"><input type="checkbox" value="${x.art}" data-name="${x.name}"${zubehoerWahl.has(x.art) ? ' checked' : ''}>mit anfragen</label></td></tr>`).join('');
}
/* ── JSON-LD nur mit sichtbaren Werten ── */
function jsonld(v) {
const props = KENN.map(k => { const w = wert(k, v); if (!w) return null; return { '@type': 'PropertyValue', name: M[k].name, value: w.w, ...(w.e ? { unitText: w.e } : {}) }; }).filter(Boolean);
$('#jsonld').textContent = JSON.stringify({ '@context': 'https://schema.org', '@type': 'Product', name: `Flächengreifer ${v.typ}`, sku: v.art, mpn: v.art, brand: { '@type': 'Brand', name: 'Vahlberg' }, additionalProperty: props });
}
/* ── Alles für die gewählte Variante setzen ── */
function zeige(v, sanft = true) {
if (gewaehlt !== v) { $('[data-ansage]').removeAttribute('data-an'); const k = $('[data-kopieren]'); k.removeAttribute('data-ok'); $('[data-kopieren-text]').textContent = 'Kopieren'; }
gewaehlt = v;
$('[data-art]').textContent = v.art;
$$('[data-typ]').forEach(e => e.textContent = v.typ);
$('[data-typzeile]').textContent = modus === 'konfiguriert' ? 'Ihre Konfiguration: ' + v.typ : 'Variante ' + v.typ;
$('[data-art-druck]').textContent = v.art;
const kurz = (k) => { const w = wert(k, v); const el = $(`[data-kurz="${k}"]`); el.innerHTML = `${w.w}<span>${w.e}</span>`; };
kurz('kraft'); kurz('laenge'); kurz('gewicht');
markiereZeile();
baueKenndaten(v);
zeichne(v, sanft);
$('[data-einheit-wort]').textContent = imperial ? 'Zoll' : 'mm';
$('[data-mass-raster]').textContent = imperial ? zahl(25 / 25.4, 2) : '25';
$('[data-max]').textContent = 'max. ' + wert('laenge', { laenge: 1800 }).w;
$('[data-massliste]').innerHTML = [['laenge', 'L'], ['breite', 'B'], ['hoehe', 'H']].map(([k, n]) => { const w = wert(k, v); return `<li><b>${n}</b> ${w.w} ${w.e}</li>`; }).join('') +
`<li><b>Saugzellen</b> ${v.zellen} im Raster ${imperial ? zahl(25 / 25.4, 2) + ' in' : '25 mm'}</li><li><b>Anschluss</b> ${v.anschluss}</li>` + (v.flansch ? '<li><b>Flansch</b> ISO 9409-1-50-4-M6</li>' : '');
baueDownloads(v); zubehoer(v); jsonld(v);
$$('[data-groessen] a').forEach(a => { if (+a.dataset.l === v.L) a.setAttribute('aria-current', 'page'); else a.removeAttribute('aria-current'); });
$('[data-extern-url]').textContent = `https://konfigurator.example/vahlberg?artikel=${encodeURIComponent(v.art)}`;
}
/* Tabelle: Zeile oder Radio wählt */
$('[data-zeilen]').addEventListener('change', e => { if (e.target.name === 'variante') zeige(BAUREIHE[+e.target.value]); });
$('[data-zeilen]').addEventListener('click', e => {
const tr = e.target.closest('tr'); if (!tr || e.target.closest('label')) return;
zeige(BAUREIHE[+tr.dataset.i]);
});
$('[data-unterschiede]').addEventListener('click', e => { nurUnterschiede = !nurUnterschiede; e.currentTarget.setAttribute('aria-checked', nurUnterschiede); spaltenFiltern(); ansage(nurUnterschiede ? 'Nur Spalten mit Unterschieden' : 'Alle Spalten'); });
$$('input[name=einheit]').forEach(r => r.addEventListener('change', () => { imperial = r.value === 'imperial' && r.checked; baueKopf(); baueZeilen(); zeige(gewaehlt, false); }));
/* Einzel: Sprungliste der Längen */
$('[data-groessen]').innerHTML = BAUREIHE.map(v => `<li><a href="#" data-l="${v.L}">${v.typ}</a></li>`).join('');
$('[data-groessen]').addEventListener('click', e => { const a = e.target.closest('a'); if (!a) return; e.preventDefault(); zeige(BAUREIHE.find(v => v.L === +a.dataset.l)); });
/* Konfiguriert: Merkmale → Artikelnummer, unmögliche Kombination begründet gesperrt */
$('[data-wahl="laenge"]').innerHTML = LAENGEN.map(L => `<label><input type="radio" name="k-laenge" value="${L}">${L}</label>`).join('');
function konfSetzen() {
const ioGeht = konf.L >= 900;
if (!ioGeht && konf.steuer === 'io') konf.steuer = 'pneu';
const ioIn = $('input[name=k-steuer][value=io]');
ioIn.disabled = !ioGeht;
$('[data-grund-io]').textContent = ioGeht ? 'IO-Link meldet Vakuum und Zustand an die Steuerung.' : 'IO-Link erst ab 900 mm Länge: kürzere Greifer haben keinen Platz für die Elektronik.';
$(`input[name=k-laenge][value="${konf.L}"]`).checked = true;
$(`input[name=k-steuer][value="${konf.steuer}"]`).checked = true;
$(`input[name=k-schaum][value="${konf.schaum}"]`).checked = true;
$(`input[name=k-flansch][value="${konf.flansch ? 1 : 0}"]`).checked = true;
zeige(variante(konf));
}
$('.konfig').addEventListener('change', e => {
const t = e.target;
if (t.name === 'k-laenge') konf.L = +t.value;
if (t.name === 'k-steuer') konf.steuer = t.value;
if (t.name === 'k-schaum') konf.schaum = t.value;
if (t.name === 'k-flansch') konf.flansch = t.value === '1';
konfSetzen();
$('[data-status]').textContent = `Artikelnummer ${gewaehlt.art}, ${gewaehlt.typ}`;
});
$('[data-extern]').addEventListener('click', () => { const h = $('[data-extern-hinweis]'); h.hidden = !h.hidden; $('[data-extern]').setAttribute('aria-expanded', !h.hidden); });
$('[data-extern-ab]').addEventListener('click', () => { $('[data-extern-hinweis]').hidden = true; $('[data-extern]').setAttribute('aria-expanded', 'false'); $('[data-extern]').focus(); });
$('[data-extern-ok]').addEventListener('click', () => ansage('Demo: Der externe Konfigurator ist hier nicht verbunden.'));
/* ── Reiter: manuelle Aktivierung (Pfeile bewegen Fokus, Eingabe/Leertaste wählt) ── */
const tabs = $$('[role=tab]');
function waehleTab(t, fokus) {
tabs.forEach(x => { const an = x === t; x.setAttribute('aria-selected', an); x.tabIndex = an ? 0 : -1; $('#' + x.getAttribute('aria-controls')).hidden = !an; });
if (fokus) t.focus();
}
tabs.forEach((t, i) => {
t.addEventListener('click', () => waehleTab(t));
t.addEventListener('keydown', e => {
let n = null;
if (e.key === 'ArrowRight') n = tabs[(i + 1) % tabs.length];
if (e.key === 'ArrowLeft') n = tabs[(i - 1 + tabs.length) % tabs.length];
if (e.key === 'Home') n = tabs[0];
if (e.key === 'End') n = tabs[tabs.length - 1];
if (n) { e.preventDefault(); tabs.forEach(x => x.tabIndex = x === n ? 0 : -1); n.focus(); }
});
});
$('[data-zu-downloads]').addEventListener('click', e => { e.preventDefault(); waehleTab($('#tab-downloads'), true); $('#tab-downloads').scrollIntoView({ block: 'start', behavior: reduziert() ? 'auto' : 'smooth' }); });
/* ── Kopieren mit Rückmeldung ── */
let ansageT;
function ansage(t) { const a = $('[data-ansage]'); a.textContent = t; $('[data-status]').textContent = t; a.setAttribute('data-an', ''); clearTimeout(ansageT); ansageT = setTimeout(() => a.removeAttribute('data-an'), 2000); }
async function kopiere(text) {
try { await navigator.clipboard.writeText(text); return true; } catch (_) {
const ta = document.createElement('textarea'); ta.value = text; ta.setAttribute('readonly', ''); ta.style.position = 'fixed'; ta.style.opacity = '0'; document.body.appendChild(ta); ta.select();
let ok = false; try { ok = document.execCommand('copy'); } catch (_) {} ta.remove(); return ok;
}
}
const okTimer = new WeakMap();
async function kopierKnopf(btn, text, still) {
const ok = still ? true : await kopiere(text);
btn.setAttribute('data-ok', '');
const label = btn.querySelector('[data-kopieren-text]'); if (label) label.textContent = ok ? 'Kopiert' : 'Markieren';
ansage(ok ? `Artikelnummer ${text} kopiert` : `Kopieren nicht möglich: ${text} bitte markieren`);
clearTimeout(okTimer.get(btn));
okTimer.set(btn, setTimeout(() => { btn.removeAttribute('data-ok'); if (label) label.textContent = 'Kopieren'; }, 2000));
}
$('[data-kopieren]').addEventListener('click', e => kopierKnopf(e.currentTarget, gewaehlt.art));
/* Downloads: Zustände Laden → Fertig (Demo, ohne echte Datei) */
$('[data-dateien]').addEventListener('click', e => {
const b = e.target.closest('[data-laden]');
if (b && !b.dataset.zustand) {
const t = b.querySelector('[data-t]'), alt = t.textContent; b.dataset.zustand = 'laedt'; b.setAttribute('aria-disabled', 'true'); t.textContent = 'Lädt …';
setTimeout(() => { b.dataset.zustand = 'fertig'; t.textContent = 'Gespeichert'; ansage('Download fertig (Demo)'); setTimeout(() => { delete b.dataset.zustand; b.removeAttribute('aria-disabled'); t.textContent = alt; }, 2000); }, 1100);
}
const c = e.target.closest('[data-cad]');
if (c) { const f = c.parentElement.nextElementSibling; f.hidden = !f.hidden; c.setAttribute('aria-expanded', !f.hidden); }
if (e.target.closest('[data-cad-ok]')) ansage('Demo: Der CAD-Dienst ist hier nicht verbunden.');
});
$('[data-dl-sprache]').addEventListener('change', () => baueDownloads(gewaehlt));
$('[data-zubehoer]').addEventListener('click', e => { const b = e.target.closest('[data-kopieren-zub]'); if (b) kopierKnopf(b, b.dataset.kopierenZub); });
$('[data-zubehoer]').addEventListener('change', e => { if (e.target.type === 'checkbox') { e.target.checked ? zubehoerWahl.add(e.target.value) : zubehoerWahl.delete(e.target.value); } });
/* ── Anfrage-Dialog: Artikelnummer wird übergeben ── */
const dlg = $('[data-dialog]'), form = $('[data-form]');
function oeffneAnfrage() {
$('[data-dlg-art]').textContent = gewaehlt.art; $('[data-dlg-typ]').textContent = gewaehlt.typ;
const zub = $$('[data-zubehoer] input:checked').map(i => i.value);
$('[data-dlg-zub]').textContent = zub.length ? zub.join(', ') : 'keins gewählt';
form.hidden = false; $('[data-ok]').hidden = true; $$('.feld', form).forEach(f => f.removeAttribute('data-fehler'));
dlg.showModal();
}
$('[data-anfragen]').addEventListener('click', oeffneAnfrage);
$$('[data-dlg-zu]').forEach(b => b.addEventListener('click', () => dlg.close()));
dlg.addEventListener('click', e => { if (e.target === dlg) dlg.close(); });
function pruefeFeld(inp) {
const f = inp.closest('.feld'), ok = inp.type === 'email' ? /\S+@\S+\.\S+/.test(inp.value) : inp.value.trim() !== '';
f.toggleAttribute('data-fehler', !ok); inp.setAttribute('aria-invalid', !ok); return ok;
}
$$('[required]', form).forEach(i => i.addEventListener('blur', () => { if (i.value) pruefeFeld(i); }));
form.addEventListener('submit', e => {
e.preventDefault();
const fehl = $$('[required]', form).filter(i => !pruefeFeld(i));
if (fehl.length) { fehl[0].focus(); return; }
const b = $('[data-senden]'); b.dataset.zustand = 'laedt'; b.setAttribute('aria-disabled', 'true'); $('[data-senden-text]').textContent = 'Wird gesendet';
setTimeout(() => {
delete b.dataset.zustand; b.removeAttribute('aria-disabled'); $('[data-senden-text]').textContent = 'Anfrage senden';
form.hidden = true; const ok = $('[data-ok]'); ok.hidden = false; $('[data-ok-art]').textContent = `${gewaehlt.art} (${gewaehlt.typ})`; ok.focus();
}, 900);
});
/* ── Druckansicht ── */
$('[data-druck]').addEventListener('click', () => { document.body.setAttribute('data-druckansicht', ''); spaltenFiltern(); scrollTo(0, 0); $('[data-druck-zu]').focus(); });
$('[data-druck-zu]').addEventListener('click', () => { document.body.removeAttribute('data-druckansicht'); spaltenFiltern(); $('[data-druck]').focus(); });
addEventListener('beforeprint', () => { document.body.setAttribute('data-druckansicht', ''); spaltenFiltern(); });
$('[data-drucken]').addEventListener('click', () => window.print());
/* ── Variante der Demo ── */
const WERTE = {
baureihe: 'power3.out · UI 200 ms · Zeichnung 350 ms · Werte ohne Animation',
einzel: 'eine Artikelnummer je Seite · Längen als Sprungliste',
konfiguriert: 'Merkmale → Artikelnummer · IO-Link ab 900 mm · extern erst nach Klick',
};
function setzeModus(m, url) {
modus = VARIANTEN.includes(m) ? m : 'baureihe';
document.body.dataset.variante = modus;
$$('.pult [data-v]').forEach(b => b.setAttribute('aria-pressed', b.dataset.v === modus));
$('[data-werte]').textContent = WERTE[modus];
$$('.nicht-baureihe').forEach(e => e.hidden = modus === 'baureihe');
$('[data-extern]').hidden = modus !== 'konfiguriert';
$('[data-extern-hinweis]').hidden = true;
if (modus === 'konfiguriert') konfSetzen(); else zeige(modus === 'einzel' ? gewaehlt : gewaehlt, false);
if (url) { const p = new URLSearchParams(location.search); p.set('v', modus); history.replaceState(null, '', '?' + p.toString().replace(/=(?=&|$)/g, '')); }
}
$$('.pult [data-v]').forEach(b => b.addEventListener('click', () => { pause(); setzeModus(b.dataset.v, true); }));
/* ── Vorführung ── */
let schleife = null, schritt = 0;
const ABLAUF = {
baureihe: [
() => zeige(BAUREIHE[2]), () => zeige(BAUREIHE[4]), () => zeige(BAUREIHE[7]),
() => $('[data-unterschiede]').click(),
() => { $('input[name=einheit][value=imperial]').click(); },
() => zeige(BAUREIHE[5]),
() => { $('input[name=einheit][value=metrisch]').click(); },
() => $('[data-unterschiede]').click(),
() => kopierKnopf($('[data-kopieren]'), gewaehlt.art, true),
() => zeige(BAUREIHE[0]),
],
einzel: [
() => zeige(BAUREIHE[5]), () => kopierKnopf($('[data-kopieren]'), gewaehlt.art, true), () => zeige(BAUREIHE[3]),
() => { $('input[name=einheit][value=imperial]').click(); }, () => { $('input[name=einheit][value=metrisch]').click(); }, () => zeige(BAUREIHE[4]),
],
konfiguriert: [
() => { konf.L = 600; konfSetzen(); }, () => { konf.steuer = 'io'; konf.L = 1200; konfSetzen(); }, () => { konf.schaum = 'ht'; konfSetzen(); },
() => { konf.flansch = true; konfSetzen(); }, () => kopierKnopf($('[data-kopieren]'), gewaehlt.art, true), () => { konf = { L: 450, steuer: 'pneu', schaum: 'std', flansch: false }; konfSetzen(); },
],
};
function takt() {
const a = ABLAUF[modus];
a[schritt % a.length](); schritt++;
schleife = setTimeout(takt, 1500);
}
function neustart() {
clearTimeout(schleife); schritt = 0;
nurUnterschiede = false; $('[data-unterschiede]').setAttribute('aria-checked', 'false');
if (imperial) { imperial = false; $('input[name=einheit][value=metrisch]').checked = true; baueKopf(); baueZeilen(); }
konf = { L: 900, steuer: 'io', schaum: 'std', flansch: false };
gewaehlt = modus === 'einzel' ? BAUREIHE[4] : BAUREIHE[4];
spaltenFiltern();
setzeModus(modus);
}
function vorfuehren() { neustart(); schleife = setTimeout(takt, 900); }
function pause() { clearTimeout(schleife); schleife = null; }
function weiter() { if (!schleife) schleife = setTimeout(takt, 400); }
baueKopf(); baueZeilen(); setzeModus(modus);
})();
</script>
</body>
</html>