/* PrintSupport — seitenspezifisches Layout: Liste, Detail, Vergleich,
   Filament-Infos, Material-Detail, Material-Navigator. */

/* ---------- Seiten-Grundbausteine ---------- */

/* Einleitungsabsatz unter einer Seitenüberschrift. Fließtextgröße, nicht
   Nebentextgröße: Er wird gelesen, nicht überflogen. Der Abstand nach unten
   trennt ihn vom ersten Abschnitt. */
.seiten-einleitung {
  font-size: var(--body-large);
  line-height: var(--zeile-text);
  color: var(--on-surface-variant);
  margin-top: var(--abstand-m);
  margin-bottom: var(--abstand-2xl);
  max-width: var(--lesebreite);
}

/* ---------- Startseite (Route „/") ----------
   Oben ein Banner über die Breite mit dem Text mittig darunter, danach die
   inhaltlichen Abschnitte zweispaltig mit wechselnder Seite: der erste mit
   dem Bild links, der nächste umgekehrt. Das führt das Auge beim Scrollen
   und lässt die Breite nicht brachliegen — vorher stand alles in einer
   schmalen linken Spalte, rechts blieb die halbe Seite leer.

   Der Kopfbereich zählt im Wechsel NICHT mit: Er hat als Banner keine Seite.
   Welcher Abschnitt gespiegelt wird, entscheidet eine einzige Bedingung in
   js/seite-start.js (themaHtml).

   Der Abstand zwischen den Abschnitten ist --abstand-2xl statt --abstand-3xl:
   Er soll gliedern, aber den nächsten Abschnitt angeschnitten stehen lassen.
   Ein leerer Bildschirm zwischen zwei Themen ist keine Großzügigkeit,
   sondern ein Loch. */

.start {
  /* Keine eigene Begrenzung mehr — die volle Breite von <main> (1600px).
     Begrenzt wird der Fließtext INNERHALB der Spalten, nicht die Seite. */
  margin: 0 auto;
}

/* --- Kopfbereich --- */

/* ⛔ ZWEISPALTIG SEIT DEM 02.09.2026 (Bildvorlage 1): links der Text, rechts
   das Logo mit der aufgesetzten Bestpreis-Karte, darunter über die volle
   Breite die Kennzahlenzeile.

   ⛔ DAS HERO-FOTO IST AM 03.09.2026 ZURÜCKGEKEHRT — fensterbreit und hinter
   dem GANZEN Kopfbereich, also auch hinter der Kennzahlenzeile. Damit sind
   die drei Bausteine wieder da, die am 02.09. entfallen waren: die
   fensterbreite Bühne, der Lesbarkeitsschleier (.kopf-schleier) und der
   Auslauf nach unten.

   ⛔ ABER NICHT ALS DIESELBE KETTE. Der frühere Aufbau hatte den Schleier als
   flache Fläche und darunter ein ZWEITES Element (.start-kopf::after) mit
   einem Farbverlauf auf --background, das die harte Kante auflösen sollte.
   Zwei unabhängig gepflegte Verläufe an derselben Naht, und genau daraus
   entstand die Kante: Fädt der Schleier früher aus als das Foto, steht unten
   unverschleiertes Bild; fädt er später, steht ein Farbband über blanker
   Seite. Beide Fälle sehen aus wie eine Linie.

   Jetzt gibt es EINE Maske (--kopf-maske), und Foto und Schleier tragen sie
   BEIDE. Sie können dadurch nicht mehr auseinanderlaufen — das ist keine
   Disziplin, sondern Bauart. Und es ist eine Maske und kein aufgelegter
   Farbverlauf (dieselbe Entscheidung wie bei den Themenbildern am
   14.08.2026): Sie nimmt Deckkraft weg, statt eine Farbe aufzutragen, kennt
   die Seitenfarbe also gar nicht und stimmt in beiden Schemata von selbst.

   ⛔ DER KOPFBEREICH IST EIN EIGENER, IN BEIDEN SCHEMATA DUNKLER GRUND.
   Der hellste Bildpunkt der Datei ist (224, 219, 216); für dunkle Schrift
   wäre kein heller Schleier stark genug, um darauf 4,5:1 zu tragen. Also
   dunkler Schleier und helle Schrift — in hell wie dunkel dieselbe. Daraus
   folgt der Rest: Die Textrollen der Seite gelten hier nicht, der Kopfbereich
   führt eigene (--kopf-tinte, --kopf-tinte-leise, --kopf-kante).

   ⚠️ Die Zusage vom 02.09. („der Kopfbereich steht in derselben Flucht wie
   alles Übrige") ist damit ausdrücklich aufgehoben — auf Entscheidung vom
   03.09.2026. Der TEXT steht weiterhin in der Flucht; nur der Grund unter ihm
   reicht bis an die Fensterkanten. */
.start-kopf {
  /* ---- Die fensterbreite Bühne ----

     ⛔ NICHT `margin-inline: calc(50% - 50vw)` als einzige Fassung, und das
     ist gemessen: 100vw schließt den klassischen Rollbalken EIN, das Layout
     bekommt aber nur die Breite ohne ihn (dieselbe Blink-Eigenheit, an der
     am 13.08.2026 die Kopfleisten-Messung hing). Unterhalb von 1024px, wo die
     Rollspule die Systemleiste nicht ersetzt, wären das rund 15px waagerechter
     Überlauf — verdeckt von `overflow-x: clip` am <html>, also lautlos.

     Bis 1720px Fenster ist der Abstand von hier zur Fensterkante exakt
     bekannt: <body> gibt --leiste-schmal ab, <main> --abstand-l, und main ist
     dort noch nicht auf 1600px gedeckelt. Diese Summe ist rollbalkenfrei.
     Erst darüber muss gerechnet werden — und dort gibt es keinen Rollbalken,
     weil die Rollspule ab 1024px greift.

     ⚠️ Die beiden Fassungen treffen sich stetig: Bei 1720px liefert die
     Rechnung 860 − 784 = 76px, also genau --leiste-schmal + --abstand-l. */
  --kopf-bleed: calc(var(--leiste-schmal) + var(--abstand-l));

  /* ---- Der Auslauf: EINE Zahl, drei Leser ----

     Sie steht in der Maske (also im Foto UND im Schleier) und ein drittes Mal
     im unteren Innenabstand. Der dritte Leser ist der wichtige: Weil der
     Inhalt über dem Polster sitzt, kann kein Text und kein Bedienelement in
     die Auslaufzone geraten — gleich wie lang die Überschrift umbricht und
     wie hoch die Kennzahlenzeile wird. Eine feste Platzreserve wäre geraten.

     Warum das kein übertriebener Sicherheitsabstand ist: Der Text ist hell und
     lebt davon, dass unter ihm das abgedunkelte Foto liegt. Im Auslauf mischt
     sich die (im Hellmodus helle) Seitenfarbe darunter — schon eine
     Beimischung von 5 % kostete beim letzten Mal gemessen 4,93 → 4,42:1. Der
     Verlauf darf den Text also nicht „kaum", sondern gar nicht berühren. */
  --kopf-auslauf: clamp(120px, 18vh, 220px);

  /* ⚠️ Die Zwischenstufen sind der Punkt, nicht Zierde. Ein glatter Anstieg
     von 0 auf 1 knickt an seinem inneren Ende, und genau diese Knickstelle
     liest das Auge als Linie — dieselbe Erfahrung wie bei den Themenbildern.
     Die Werte sind die von dort (0,18 / 0,58). */
  --kopf-maske: linear-gradient(
    to bottom,
    #000 0,
    #000 calc(100% - var(--kopf-auslauf)),
    rgba(0, 0, 0, 0.58) calc(100% - var(--kopf-auslauf) * 0.55),
    rgba(0, 0, 0, 0.18) calc(100% - var(--kopf-auslauf) * 0.25),
    transparent 100%
  );

  /* ---- Die eigenen Rollen des Kopfbereichs ----

     ⛔ IN BEIDEN SCHEMATA DIESELBEN. Der Grund steht oben: Der Schleier ist in
     beiden dunkel, also ist die Schrift in beiden hell. Wer hier eine
     Schema-Rolle einsetzt (--on-surface, --outline-variant …), bekommt im
     Hellmodus dunkle Schrift auf dunklem Grund — und zwar ohne Fehlermeldung.

     Die Deckkraft ist GERECHNET, nicht gewählt: Gegen den hellsten Bildpunkt
     der Datei (224, 219, 216) trägt weiße Schrift bei 0,62 noch 6,49:1. Das
     ist die untere Schranke über das ganze Bild — jede wirkliche Stelle liegt
     darüber. */
  --kopf-schleier-farbe: rgba(12, 14, 18, 0.62);
  --kopf-tinte: #FFFFFF;
  --kopf-tinte-leise: #D7DBE3;
  /* ⛔ 0,55 IST GEMESSEN, NICHT GEWÄHLT. Mit 0,32 trug die Haarlinie gegen den
     verschleierten Grund nur 2,4–2,8:1 und verfehlte die 3:1 aus WCAG 1.4.11.
     Sie muss zwei Prüfungen zugleich bestehen, und das ist der Grund für
     einen so hohen Wert: gegen die DUNKELSTE Stelle des Fotos (dort ist der
     Grund fast schwarz) und gegen die HELLSTE (dort ist er rgb(95,94,96)).
     Bei 0,55 sind es 5,8:1 bzw. 3,2:1 — beide über der Schwelle.

     ⚠️ Warum die Kante das überhaupt allein tragen muss: Im Dunkelmodus ist
     die Füllung der Pillen --surface-container (#181B21), und der
     verschleierte Grund ist an manchen Stellen exakt so dunkel — gemessen
     1,00:1. Die Pille verschwindet dort in der Fläche. Die Füllungen bleiben
     trotzdem ihre Schema-Rollen (der Text darauf ist einwandfrei lesbar,
     13,8:1); getauscht wird die Kante, nicht das Schema. */
  --kopf-kante: rgba(255, 255, 255, 0.55);

  /* ⚠️ Die Trennlinie über der Kennzahlenzeile ist NICHT dieselbe Rolle, auch
     wenn beide dünne weiße Striche sind: Sie gliedert, sie grenzt kein
     Bedienelement ab. Für sie gilt keine 3:1-Schwelle, und mit 0,55 stünde
     sie lauter da als die Kennzahlen darunter. */
  --kopf-linie: rgba(255, 255, 255, 0.26);

  /* ⛔ DIE FÜLLUNGEN VON SUCHFELD, CHIPS UND BESTPREIS-KARTE BLEIBEN IHRE
     SCHEMA-ROLLEN — nur ihre KANTE wird hell. Der Grund ist die Trennung von
     zwei Fragen: Ihr TEXT steht auf ihrer eigenen deckenden Fläche und ist
     dort in beiden Schemata einwandfrei lesbar (nichts daran zu ändern); ihre
     GRENZE steht gegen das Foto, und im Dunkelmodus ist eine dunkle Pille auf
     dunklem Grund mit einer dunklen Haarlinie (--outline-variant, #3D434B)
     nicht mehr abzugrenzen. Eine helle Haarlinie trägt in beiden Schemata:
     hell ist sie auf der weißen Pille ohnehin belanglos, dort trägt die
     Füllung. Die Rollen umzudefinieren statt jede Regel einzeln anzufassen,
     hält es bei EINER Stelle — auch für das, was hier später dazukommt. */
  --karten-kante: 0 0 0 1px var(--kopf-kante);
  --feld-kante: 0 0 0 1px var(--kopf-kante);

  position: relative;
  /* Hebt main's padding-top auf: Das Foto beginnt unmittelbar unter der
     Kopfleiste, so wie ein Hero es soll. Die Luft über der Überschrift kommt
     aus dem Innenabstand darunter, also von INNERHALB der Bühne. */
  margin-top: calc(-1 * var(--abstand-xl));
  margin-inline: calc(-1 * var(--kopf-bleed));
  /* Holt den Inhalt in die Flucht zurück — der Grund ist fensterbreit, der
     Text nicht. */
  padding-inline: var(--kopf-bleed);
  padding-top: var(--abstand-3xl);
  padding-bottom: calc(var(--abstand-3xl) + var(--kopf-auslauf));
  color: var(--kopf-tinte);
}

/* Unter 640px steht `body { padding-inline: 0 }` (css/layout.css) — das Token
   --leiste-schmal behält dort seine 60px, die Seite gibt sie nur nicht mehr
   ab. Wer hier die Rechnung von oben stehenließe, ließe die Bühne 60px je
   Seite über das Fenster hinausragen. */
@media (max-width: 639px) {
  .start-kopf { --kopf-bleed: var(--abstand-l); }
}

/* Ab hier ist <main> auf 1600px gedeckelt und der Abstand zur Fensterkante
   wächst mit dem Fenster — er lässt sich nicht mehr aus Token summieren.
   1024px sind längst überschritten, die Rollspule hat die Systemleiste also
   ersetzt: vw ist hier gefahrlos. */
@media (min-width: 1721px) {
  .start-kopf { --kopf-bleed: calc(50vw - 50%); }
}

/* ---------- ⛔ ZWEI SCHRIFTEN IM KOPFBEREICH, NICHT DREI (03.09.2026) -------

   Gemessen an der laufenden Seite trug der Kopfbereich drei Familien:
   system-ui (11 Knoten), Source Serif 4 (5) und JetBrains Mono (5). Auf
   Ansage bleiben zwei:

     Source Serif 4   AUSSCHLIESSLICH die h1 („Filament- und
                      Resin-Preisvergleich für den 3D-Druck", seit 28.09.2026)
     system-ui        alles andere — auch der Claim „Print smart. Pay
                      less." darunter (.start-claim)

   Weggefallen sind damit die Serif an `.bestpreis-wert` und `.kennzahl-wert`
   und die Monospace an `.kicker`, `.bestpreis-zeile`, `.bestpreis-hinweis`
   und `.datenstand`.

   ⛔ DAS IST EINE AUSNAHME VON DER TYPOGRAFIEREGEL DES PROJEKTS, und sie
   gehört benannt: Sonst gilt „was gemessen oder abgelesen ist, steht in
   Schreibmaschinenschrift; was die Seite gliedert, in Serif — Überschriften,
   Preise, Kennzahlen" (siehe css/theme.css). Im Kopfbereich gilt sie nicht
   mehr. Folge: „ab 19,65 €/kg" steht hier grotesk, derselbe Preis auf jeder
   Kachel und in der Kaufbox weiterhin in Serif. Wer die Regel wiederherstellt,
   tut es für den ganzen Kopfbereich oder gar nicht — eine halb umgestellte
   Fläche ist schlechter als beide Fassungen.

   ⛔ `.kicker` IST EIN GETEILTER BAUSTEIN und wird deshalb NUR HIER
   umgestellt, nicht in css/komponenten.css. Er trägt sonst die Versalienzeile
   auf /wissen (sechs Stellen) und auf der Detailseite („Vertrauen", „So
   entsteht der Preis pro Kilogramm"); dort kennzeichnet die Monospace
   weiterhin technische Werte. Eine Änderung an der Grundregel nähme sie
   diesen Stellen mit weg, ohne dass hier etwas auffiele.

   ⚠️ Die übrigen vier Träger stehen ausschließlich in js/seite-start.js und
   sind deshalb in ihrer eigenen Regel geändert statt hier überschrieben. */
.start-kopf .kicker {
  font-family: var(--schrift);
}

/* ---------- Foto und Schleier ----------

   Zwei Schichten, gleiche Fläche, gleiche Maske. Beide `inset: 0` im
   Polsterkasten der Bühne, also fensterbreit; der Inhalt darüber steht in der
   Flucht, weil ihn der Innenabstand dorthin schiebt. */
.kopf-foto,
.kopf-schleier {
  position: absolute;
  inset: 0;
  /* Die Schichten sind Grund, kein Ziel: Ein Klick auf die Überschrift darf
     nicht an einer Fläche darüber hängenbleiben — und das Foto trägt alt="",
     ist also auch für Vorlesehilfen nichts. */
  pointer-events: none;
  /* ⚠️ Beide ausdrücklich auf 0, der Inhalt auf 1. Ohne Angabe entschiede
     allein die Dokumentreihenfolge, und die nächste Schicht, die hier jemand
     einhängt, stünde je nach Einfügestelle über dem Text. */
  z-index: 0;
  /* -webkit- zuerst, die genormte Zeile gewinnt: Wer beide versteht, nimmt
     die letzte; wer nur die alte kennt, überliest die genormte. */
  -webkit-mask-image: var(--kopf-maske);
  mask-image: var(--kopf-maske);
}

.kopf-foto {
  width: 100%;
  height: 100%;
  /* Formatfüllend und unverzerrt. `object-position: 30%`: Die vorderste blaue
     Spule sitzt im linken Bilddrittel; bei mittigem Zuschnitt bliebe sie auf
     schmalen Fenstern halb weg (dort ist nur rund ein Drittel der Bildbreite
     zu sehen). */
  object-fit: cover;
  object-position: 30% 50%;
  /* basis.css setzt `img { max-width: 100% }` — hier unschädlich, aber die
     Höhe braucht die ausdrückliche Angabe, sonst zöge das 3:2-Bild die
     Bühne auf sein eigenes Verhältnis. */
  max-width: none;
}

.kopf-schleier {
  background: var(--kopf-schleier-farbe);
}

/* Der Inhalt über den beiden Schichten. */
.start-kopf > .start-kopf-raster,
.start-kopf > .start-kennzahlen {
  position: relative;
  z-index: 1;
}

/* ---------- Was auf dem Schleier steht ----------

   ⛔ NUR DAS, WAS WIRKLICH AUF DEM FOTO LIEGT. Der Kicker der Bestpreis-Karte
   heißt genauso und steht auf der DECKENDEN Kartenfläche — er behält seine
   Schema-Rolle. Ein pauschales `.start-kopf .kicker` färbte ihn mit ein und
   setzte im Hellmodus weiße Schrift auf eine weiße Karte. Deshalb sind die
   beiden freistehenden Kicker einzeln benannt.

   h1 und .kennzahl-wert stehen bewusst nicht in dieser Liste: Sie führen gar
   keine eigene Farbe und erben --kopf-tinte von der Bühne. */
.kopf-text > .kicker,
.start-schnellsuchen > .kicker {
  color: var(--kopf-tinte-leise);
}

/* ⚠️ .start-claim gehört hierher und NICHT an --on-surface-variant: Die
   Unterzeile liegt auf dem Foto, und nur die Kopfrolle ist gegen den Schleier
   gemessen. */
.start-kopf .start-claim,
.start-kopf .start-satz,
.start-kopf .kennzahl-text {
  color: var(--kopf-tinte-leise);
}

/* Die Trennlinie über der Kennzahlenzeile liegt jetzt ebenfalls auf dem Foto.
   --outline-variant ist hell #DCDFE6 und dunkel #3D434B — die eine wäre auf
   dem dunklen Schleier grell, die andere unsichtbar. */
.start-kopf .start-kennzahlen {
  border-top-color: var(--kopf-linie);
}

.start-kopf-raster {
  display: grid;
  /* ⚠️ minmax(0, 1fr), nicht die stillschweigende auto-Spur: Eine Rasterspur
     wird von sich aus nie schmaler als der Inhalt, den sie trägt. Mit dem
     Vorgabewert drückte die Suchzeile die einzige Spalte auf ihre eigene
     Mindestbreite auf — dieselbe Falle wie bei .ordner-karte und
     .sortier-option. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--abstand-2xl);
  align-items: center;
}

/* Zwei Spalten erst, wenn beide etwas zu zeigen haben. Darunter steht der
   Text über dem Bild — nicht umgekehrt: Die Überschrift ist die Aussage der
   Seite, das Bild begleitet sie. */
@media (min-width: 900px) {
  .start-kopf-raster {
    /* 1,15fr: Die Textspalte trägt Überschrift, Fließtext, Suchfeld und vier
       Schnellsuchen, die Bildspalte ein Bild. Gleiche Spalten ließen die
       Schnellsuchen bei 1024px in drei Zeilen umbrechen. */
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    gap: var(--abstand-3xl);
  }
}

/* Die Bildspalte. `position: relative` ist der Bezug der aufgesetzten Karte
   darunter — sie liegt absolut über der unteren Bildkante. */
.start-kopf-bild {
  position: relative;
}

/* ---------- Das Logo im Kopfbereich (02.09.2026) ----------

   ⛔ ES STEHT FREI AUF DER SEITENFLÄCHE: kein Zuschnitt, kein Rahmen, keine
   Abdunklung, keine Maske. Das PNG ist ringsum transparent und braucht
   deshalb keinen Übergang — es HAT keine Bildkante.

   ⚠️ Containerkontext: Die Größe zählt in Prozent der SPALTE (cqw), nicht des
   Fensters. Zwischen 900 und 899px springt die Spalte um mehr als das
   Doppelte; dieselben Pixel wären dort einmal ein Drittel und einmal die
   halbe Spaltenbreite. */
.start-kopf-bild {
  container-type: inline-size;
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* ⛔ DAS LOGO IST SEIT DEM 07.09.2026 DIE BILDMARKE, und die ganze Rechnung
   dahinter ist entfallen: --logo-kasten, --logo-rand, --spulen-fuellung und
   `max-width: none`. Sie glichen den transparenten Rand von logo-spule.png
   aus (57,4 % sichtbar); die neue Zeichnung hat keinen — gemessen viewBox =
   Inhalt, alle vier Ränder 0,000. Sichtbare Größe und Kastenmaß sind hier
   also dasselbe, und es gibt nichts mehr umzurechnen.

   ⛔ IM KOPFBEREICH IST ES SEIT DEM 23.09.2026 DIE FILAMENTSPULE
   (assets/start-logo-spule.webp), ein Foto in einem <img>. Die Klasse trägt
   daneben weiterhin die Bildmarke der Themen-PLATZHALTER (LOGO_HTML in
   js/seite-start.js) — dort bleibt es ein Inline-SVG mit `currentColor`.
   Beide Fassungen teilen sich nur das Maß, und das ist der Zweck der Regel.

   ⚠️ ZWEI MARKEN AUF EINER SEITE SIND ES TROTZDEM NICHT: Die Spule trägt
   die Marke auf ihrem Etikett, sie ersetzt sie nicht. Bild- und Wortmarke
   stehen unverändert in der Kopfleiste darüber.

   ⚠️ DIE GRÖSSE IST UNVERÄNDERT, und das ist kein Zufall, sondern
   gemessen: Die Bildmarke steht 41 : 42 (0,976), der Zuschnitt der Spule
   665 : 680 (0,978). Bei gleicher Höhe ist auch die Breite dieselbe — der
   Tausch verschiebt in dieser Spalte nichts.

   Die Grenzen sind unverändert: unter 150px trägt es die Spalte nicht mehr,
   über 300px erdrückt es die Textseite gegenüber. Dazwischen wächst es mit
   der Spalte. ⚠️ Der Wert MEINT wirklich, was er sagt — in der Fassung vor
   dem 07.09.2026 war er die sichtbare Spule in einem größeren Kasten. */
.kopf-logo {
  /* ⛔ BEMESSEN WIRD DIE HÖHE, DIE BREITE FOLGT. Über die Breite bemessen
     hinge die Höhe am Seitenverhältnis der jeweiligen Fassung, und die
     beiden sind zwar fast, aber nicht genau gleich — die Bestpreis-Karte
     darunter stünde je Fassung ein paar Pixel anders.

     ⛔ UND NUR EINE DER BEIDEN ACHSEN DARF GESETZT SEIN. Beim Bauen stand
     hier `width: clamp(…)` UND `max-height: clamp(…)`; sobald die Höhe
     band, blieb die Breite stehen und der Kasten war gequetscht — gemessen
     0,7667 statt 0,7182 bei 1440px. Sichtbar wäre es kaum gewesen, weil das
     SVG seinen Inhalt proportional einpasst: Die Zeichnung hätte richtig
     ausgesehen und in einem zu breiten Kasten gestanden. Der Prüffall hat
     es gefunden.

     ⚠️ Das <img> trägt dafür width/height als ATTRIBUTE: Daraus kennt der
     Browser das Seitenverhältnis, bevor die Datei da ist, und hält den
     Platz frei. Ohne sie springt die Bestpreis-Karte beim Eintreffen des
     Bildes um die volle Logohöhe nach unten.

     Die Höhe wächst mit der SPALTENbreite (cqw), nicht mit dem Fenster —
     dieselbe Bezugsgröße wie vorher, nur auf der anderen Achse. */
  height: clamp(180px, 46cqw, 340px);
  width: auto;
  /* Als <img> sonst inline: Es bekäme den halben Durchschuss seiner
     Zeilenbox als Lücke darunter, und die 16px Luft zur Karte wären in
     Wahrheit 20. */
  display: block;
  /* Die 16px Luft zwischen Logo und Karte. Der frühere negative Rand nahm den
     transparenten Saum zurück; ohne Saum ist es ein gewöhnlicher Abstand. */
  margin-bottom: var(--abstand-l);
}

/* ---------- Die aufgesetzte Bestpreis-Karte ----------

   Sie liegt über der unteren rechten Bildkante und zeigt das aktuell
   günstigste lieferbare Filamentangebot (js/zustand.js, startKennzahlen).

   ⚠️ EINER DER WENIGEN ECHTEN SCHATTEN DER SEITE. Die Regel des Projekts
   lautet, dass --schatten-schwebend echten Überlagerungen vorbehalten bleibt
   (Menü, Sheet, Snackbar, Vergleichsleiste) — und genau das ist diese Karte:
   Sie liegt sichtbar ÜBER dem Bild. Die Kartenkante bekommt sie zusätzlich,
   weil sie im gestapelten Layout auf schmalen Fenstern unter das Bild rutscht
   und dort nichts mehr überlagert. */
/* ⛔ SIE LIEGT NICHT MEHR ABSOLUT ÜBER DEM MOTIV (02.09.2026, mit dem Logo).
   Über einem randfüllenden Foto war „unten rechts aufgesetzt" richtig — dort
   verdeckte sie Bildfläche, von der es reichlich gab. Über einem Logo geht das
   nicht: Es ist eine FORM, und eine Karte, die eine Ecke davon abschneidet,
   beschädigt die Marke.

   Im Fluss unter dem Bild, aber mit dem negativen Rand des Logos darüber
   (siehe .kopf-logo) sitzt sie dicht an der sichtbaren Spulenkante — optisch
   also weiterhin am Motiv, geometrisch garantiert daneben. Rechtsbündig, wie
   in der Vorlage. */
.start-bestpreis-feld {
  align-self: end;
  max-width: min(320px, 100%);
}

/* ⛔ UNTER 640px RÜCKT SIE GANZ UNTER DAS MOTIV: volle Breite, mittig.
   Auf 320px steht das Logo fast so breit wie die Spalte, und eine 320px-Karte
   dicht an seiner Silhouette liest sich als Fehler, nicht als Absicht.
   ⚠️ Der frühere Zusatz „ohne die Überlappung in den transparenten Rand" ist
   mit dem Rand entfallen (07.09.2026). */
@media (max-width: 639px) {
  .kopf-logo { margin-bottom: 0; }
  .start-bestpreis-feld {
    align-self: stretch;
    max-width: none;
    margin-top: var(--abstand-l);
  }
}

.start-bestpreis,
.start-bestpreis--wartet {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-xxs);
  padding: var(--abstand-l) var(--abstand-xl);
  border-radius: var(--radius-l);
  background: var(--surface-container);
  box-shadow: var(--karten-kante), var(--schatten-schwebend);
  color: var(--on-surface);
  text-decoration: none;
}

.bestpreis-wert {
  font-size: var(--headline-medium);
  font-weight: 700;
  line-height: var(--zeile-eng);
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
  /* ⚠️ NICHT in --primary. Der Preis ist hier eine Auskunft, kein Ziel: Der
     ganze Kasten ist der Link, und ein Preis in Linkfarbe darin läse sich als
     zweiter, eigener Verweis. Auf den Kacheln und in der Kaufbox steht er
     weiterhin in der Akzentfarbe — dort ist er der Vergleichswert und trägt
     die Rangfolge. */
  color: var(--on-surface);
}

.bestpreis-zeile,
.bestpreis-hinweis {
  font-size: var(--body-small);
  line-height: var(--zeile-normal);
  color: var(--on-surface-variant);
}

/* Der Text steht links, nicht mehr mittig auf dem Foto — und in der
   gewöhnlichen Textfarbe der Seite. Die frühere Sonderregel (helle Schrift in
   BEIDEN Schemata, weil das Foto in beiden dunkel ist) ist mit dem Schleier
   entfallen. */
.kopf-text {
  max-width: var(--lesebreite);
}

.start-kopf h1 {
  /* Die eine Stelle, an der --display-large steht: die Aussage der Seite.
     clamp, weil 64px auf einem 320px-Fenster nicht zu haben sind — dort
     bleiben es die 34px der übrigen Seitentitel. */
  font-size: clamp(var(--headline-large), 1.5rem + 3.2vw, var(--display-large));
  line-height: 1.02;
  letter-spacing: -0.025em;
  /* Seit dem 28.09.2026 trägt die h1 den Suchbegriff („Filament- und
     Resin-Preisvergleich für den 3D-Druck"), der Claim steht als Unterzeile
     darunter. ⚠️ Die frühere Grenze von 12ch passte zu „Print smart. Pay
     less." und ist hier zu schmal: „Resin-Preisvergleich" bräche nach dem
     Bindestrich, und „Preisvergleich" allein ragte bei großer Schrift über
     die Grenze. Die Zeilen gleicht `text-wrap: balance` aus. */
  max-width: 20ch;
  text-wrap: balance;
  /* Bei großer Schrift auf schmalen Fenstern ist „Preisvergleich" breiter
     als die Spalte. Getrennt wird dann nach Silben (lang="de"), sonst am
     Rand — überlaufen darf die Zeile nicht. */
  hyphens: auto;
  overflow-wrap: anywhere;
}

/* Der Claim als Unterzeile der h1 — grotesk, denn die Serif trägt im
   Kopfbereich ausschließlich die h1. Der Abstand zur h1 ist klein (sie gehören
   zusammen), der zum Einleitungssatz darunter bringt .start-satz selbst mit. */
.start-claim {
  margin-top: var(--abstand-s);
  font-size: var(--title-large);
  font-weight: 600;
  line-height: var(--zeile-normal);
}

.start-satz {
  margin-top: var(--abstand-l);
  font-size: var(--title-medium);
  line-height: var(--zeile-text);
  max-width: 46ch;
  color: var(--on-surface-variant);
}

/* Suchzeile: eine Fläche, in der Feld und Knopf zusammen sitzen — dieselbe
   Bauform wie das Suchfeld über der Angebotsliste, nur größer.

   ⚠️ Der Knopf „Jetzt vergleichen" darunter ist am 02.09.2026 entfallen; sein
   Weg lebt als vierte Schnellsuche („Alle Angebote") weiter. Die Begründung
   steht bei SCHNELLSUCHEN in js/seite-start.js. */
.start-suche {
  display: flex;
  align-items: center;
  gap: var(--abstand-s);
  margin-top: var(--abstand-xl);
  padding: var(--abstand-xs) var(--abstand-xs) var(--abstand-xs) var(--abstand-l);
  border-radius: var(--radius-pille);
  background: var(--surface-container);
  /* Die Feldkante ist jetzt die einzige Abgrenzung: Das Feld ist weiß, der
     Grund fast weiß (Gestaltungswechsel 02.09.2026). */
  box-shadow: var(--feld-kante);
  max-width: 560px;
  text-align: left;
  color: var(--on-surface);
}

.start-suche-symbol {
  display: inline-flex;
  flex: 0 0 auto;
  color: var(--on-surface-variant);
}
.start-suche-symbol svg { width: 20px; height: 20px; }

.start-suche input {
  flex: 1;
  /* ⛔ BEIDES, min-width UND width — und das ist gemessen, nicht vorsorglich.
     `min-width: 0` nimmt dem Feld nur die automatische Untergrenze; seine
     min-content-BREITE bleibt die intrinsische Größe des Eingabefelds
     (Chrome rechnet mit rund 20 Zeichen, gemessen 169px). Diese Breite geht
     in den Beitrag der ganzen Suchzeile ein: 303,1px min-content, obwohl die
     Spalte bei 320px Fenster nur 273px hat. Die Zeile drückte damit die
     Rasterspur auf und die Startseite bekam 14px waagerechten Überlauf, den
     `overflow-x: clip` am <html> lautlos verdeckte.
     Mit `width: 0` ist der Beitrag null und `flex: 1` verteilt den Platz wie
     zuvor — sichtbar ändert sich nichts, gemessen alles. */
  width: 0;
  min-width: 0;
  border: 0;
  background: none;
  outline: none;
  padding: var(--abstand-m) 0;
  font-size: var(--body-large);
}
.start-suche input::placeholder { color: var(--on-surface-variant); }

/* Der Knopf darf nicht mitschrumpfen: Auf einem 360px-Fenster würde ihn das
   Eingabefeld sonst zusammendrücken, bis „Suchen" umbricht oder die
   Trefferfläche zu klein wird. Das Feld gibt nach, der Knopf nicht. */
.start-suche .knopf { flex: 0 0 auto; }

/* ⚠️ SCHMALE GERÄTE: die Zeile rückt zusammen, der Knopf bleibt (02.09.2026).

   Feld und Knopf überlagern sich auf keiner Breite — zwischen ihnen stehen
   immer 8px. Eng wird allein der Text IM Feld: Bei 360px Gerätebreite blieben
   dem Eingabefeld 193,9px, und die mittlere Fassung des Platzhalters
   („Marke, Material oder Farbe") braucht 190,2px — sie fiel um 2,3px durch
   und wurde auf die kurze Stufe zurückgestuft.

   Die 12px, die Innenabstand und Lücken hier abgeben, holen genau das zurück:
   gemessen 205,9px statt 193,9px bei 360px, und damit steht dort wieder die
   mittlere Fassung. Was gekürzt wird, ist Luft — der Knopf behält seine
   Breite und seine 40px Höhe, das Symbol bleibt stehen. */
@media (max-width: 480px) {
  .start-suche {
    gap: var(--abstand-xs);
    padding-left: var(--abstand-m);
  }
}

/* ---------- Schnellsuchen („Häufig gesucht") ----------

   Vier umrandete Pillen unter dem Suchfeld. Sie sind echte Links mit
   Abfrageteil (/filamente?suche=…), keine Knöpfe mit Klick-Zuhörer — die
   Begründung steht bei SCHNELLSUCHEN in js/seite-start.js.

   ⚠️ Sie sehen aus wie Filter-Chips und sind bewusst KEINE: Ein Chip zeigt
   einen Zustand („dieses Kriterium ist gesetzt"), diese hier führen weg. Sie
   tragen deshalb weder Häkchen noch einen gewählten Zustand. */
.start-schnellsuchen {
  margin-top: var(--abstand-xl);
}

.schnellsuche-reihe {
  display: flex;
  flex-wrap: wrap;
  gap: var(--abstand-s);
  margin-top: var(--abstand-s);
}

.schnellsuche {
  display: inline-flex;
  align-items: center;
  /* 36px statt der 40px eines Knopfes: Sie sind ein Angebot, kein Hauptweg.
     Als Link mit Text bleibt die Trefferfläche über der Mindestgröße. */
  min-height: 36px;
  padding: 0 var(--abstand-l);
  border-radius: var(--radius-pille);
  background: var(--surface-container);
  box-shadow: var(--feld-kante);
  font-size: var(--label-large);
  font-weight: 500;
  color: var(--on-surface);
  text-decoration: none;
  transition: background-color var(--dauer-kurz) ease;
}

.schnellsuche:hover { background-color: var(--zustand-hover); }
.schnellsuche:active { background-color: var(--zustand-aktiv); }

/* ---------- Kennzahlenzeile ----------

   Drei gezählte Werte und der Datenstand, abgesetzt durch eine Linie über die
   volle Breite — wie in der Vorlage.

   ⛔ JEDE ZAHL IST GEZÄHLT (js/zustand.js, startKennzahlen). Die Vorlage zeigt
   hier „42.800 verglichene Angebote"; im Bestand stehen rund 550. Und der
   grüne Badge „Preise aktualisiert vor 12 Min" hat KEINE Entsprechung in den
   Daten — weder Produkt noch Angebot führen einen Zeitstempel. An seiner
   Stelle steht die Auskunft, die wir wirklich haben: ob die Preise vom Server
   kommen oder aus den Beispieldaten. */
.start-kennzahlen {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--abstand-l) var(--abstand-3xl);
  margin-top: var(--abstand-3xl);
  padding-top: var(--abstand-2xl);
  border-top: 1px solid var(--outline-variant);
}

.kennzahl {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-xxs);
}

.kennzahl-wert {
  font-size: var(--headline-small);
  font-weight: 600;
  line-height: var(--zeile-eng);
  font-variant-numeric: tabular-nums;
}

.kennzahl-text {
  font-size: var(--body-medium);
  color: var(--on-surface-variant);
}

/* Der Datenstand rückt ans Ende der Zeile — auf schmalen Fenstern bricht er
   von selbst in die nächste. */
.datenstand {
  display: inline-flex;
  align-items: center;
  gap: var(--abstand-s);
  margin-left: auto;
  padding: var(--abstand-xs) var(--abstand-m);
  border-radius: var(--radius-pille);
  background: var(--gruen-container);
  font-size: var(--body-small);
  color: var(--gruen-inhalt);
}

/* Der Punkt davor entsteht als Pseudoelement: Er ist Zierde und hat im
   Markup nichts verloren — eine Vorlesehilfe würde ihn sonst ankündigen. */
.datenstand::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
}

.datenstand--wartet {
  background: var(--surface-container-highest);
  color: var(--on-surface-variant);
}

/* ⚠️ Der Testmodus-Fall trägt die WARNROLLE, nicht die Fehlerrolle: Die Seite
   funktioniert, sie zeigt nur Beispieldaten. Über der Liste steht in diesem
   Fall ohnehin schon das Testmodus-Band; hier geht es allein darum, dass die
   Kennzahlen daneben nicht als Live-Bestand gelesen werden. */
.datenstand--test {
  background: var(--surface-container-highest);
  color: var(--warnung-erhoeht);
}

@media (max-width: 640px) {
  .start-kennzahlen {
    gap: var(--abstand-l) var(--abstand-xl);
    margin-top: var(--abstand-2xl);
    padding-top: var(--abstand-xl);
  }
  /* Auf schmalen Fenstern steht der Datenstand unter den Zahlen statt am
     rechten Rand — `margin-left: auto` schöbe ihn sonst in eine eigene Zeile
     ganz rechts, wo er wie ein abgeschnittener Rest aussieht. */
  .datenstand {
    margin-left: 0;
    flex-basis: 100%;
  }
}

/* --- Bildflächen ---
   Vorbereitet für spätere Fotos, heute mit der vorhandenen Spulen-Grafik
   belegt. Das Seitenverhältnis steht hier und nicht in der Bilddatei: Ein
   später eingesetztes Foto beliebiger Größe füllt dieselbe Fläche, ohne dass
   das Layout springt. */

/* ⛔ 3:2 UND VOLLSTÄNDIG EINGEPASST (12.08.2026, zweite Fassung).

   Die erste Fassung desselben Tages stand auf 4:3 mit `object-fit: cover` und
   nahm den Beschnitt in Kauf. Das war ein Fehler, und er ist gemeldet worden:
   Auf der Druckerübersicht war der linke Drucker seitlich angeschnitten.
   Gemessen kostete 4:3 die 3:2-Vorlagen (4/3)÷(3/2) = 11,1 % ihrer Breite,
   also 85px LINKS UND RECHTS — genau an den Kanten, an denen dort Geräte
   stehen.

   3:2 ist das Verhältnis von DREI der vier Dateien und des Kopfbildes. Für
   sie fällt damit nichts mehr weg. Und weil die Fläche ihr Verhältnis
   unabhängig von der Fensterbreite hält, gilt das auf JEDER Breite — auch
   auf dem Handy, wo der Beschnitt sonst am ehesten auffiele.

   ⚠️ `contain` statt `cover`, und das ist die eigentliche Zusage: Es wird
   NICHTS abgeschnitten. Für die drei 3:2-Dateien ist das folgenlos — bei
   gleichem Verhältnis füllt `contain` die Fläche genauso aus wie `cover`.
   Wirksam wird es allein bei `start-drucker.jpg`, der einzigen 1:1-Datei:
   Sie steht vollständig da und bekommt dafür 125px Rand links und rechts
   (16,7 % je Seite). Im dunklen Schema fällt der Rand kaum auf, im hellen
   deutlich — auf Entscheidung vom 12.08.2026 als Zwischenstand hingenommen,
   bis die Datei im 3:2 nachgeliefert wird. Danach verschwindet der Rand von
   selbst, ohne dass hier etwas zu ändern wäre.

   ⛔ Wer wieder auf `cover` zurückstellt, schneidet sofort wieder ab. Wer
   das Verhältnis ändert, prüft vorher die Maße ALLER Dateien in
   js/seite-start.js — sie stehen dort als breite/hoehe an jedem Bildeintrag. */
.bildflaeche {
  position: relative;
  aspect-ratio: 3 / 2;
  /* ⛔ KEINE Rundung und KEIN Zuschnitt mehr (14.08.2026). Beide gehörten
     zusammen: `overflow: hidden` war allein dazu da, das Bild auf die
     gerundete Ecke zu beschneiden. Mit dem weichen Auslauf (siehe
     `.bildflaeche--foto img` weiter unten) hat das Bild überhaupt keine
     sichtbare Kante mehr, auf die sich eine Rundung legen ließe — eine
     gerundete Ecke an einem Bild, das ringsum ausläuft, wäre eine Kante an
     einer Stelle, an der es keine gibt.

     ⚠️ Nachgemessen, bevor der Zuschnitt fiel: Bei keiner der vier Bildarten
     und auf keiner Breite ragt ein Kind über die Fläche hinaus — die Fotos
     sind auf `max-width/max-height: 100%` begrenzt, Grafik und Platzhalter
     auf 55 % bzw. `min(45%, 180px)`. Es gibt nichts mehr zu beschneiden. */
  /* ⛔ `min-width: 0` IST DER PREIS FÜR DEN WEGFALL VON `overflow: hidden` —
     und ohne es ist der Wegfall ein Fehler. Beim Bauen genau so passiert und
     nachgemessen:

       mit overflow: hidden    Fenster 320  →  Herz-Fläche 273 × 240
       ohne, ohne min-width    Fenster 320  →  Herz-Fläche 360 × 240

     Der Grund liegt nicht am Zuschnitt, sondern an einer Nebenwirkung, die er
     hatte: Ein Rasterkind bekommt von sich aus `min-width: auto`, also die
     Größe seines Inhalts als Untergrenze — es sei denn, sein Überlauf steht
     auf etwas anderem als `visible`, dann ist die Untergrenze 0. Genau das
     hat `overflow: hidden` hier nebenbei getan.

     Fällt es weg, greift die Untergrenze wieder. `.bildflaeche--herz` ist die
     einzige Bauart mit eigener `min-height` (240px); über `aspect-ratio:
     3 / 2` rechnet die Untergrenze daraus eine BREITE zurück (240 × 1,5 =
     360px) und überstimmt damit die Rasterbreite von 273px. Ergebnis war ein
     waagerechter Überlauf des Dokuments von 71px, den allein `overflow-x:
     clip` am `<html>` verdeckt hat — ein Fehler also, der sichtbar nichts
     kaputtmacht und deshalb lange unbemerkt bliebe.

     ⚠️ Die Falle ist, daß sie nach dem Zuschnitt aussieht und keine ist.
     `overflow: hidden` wieder einzusetzen „behöbe" sie ebenfalls und
     schriebe den Zusammenhang wieder zu. Dieselbe Untergrenze ist im Projekt
     schon zweimal aufgeschlagen (`.ordner-karte`, `.sortier-option`) und
     wird dort mit derselben Zeile beantwortet.

     Nachgemessen auf zwölf Breiten von 1920 bis 320: Alle fünf Flächen sind
     Pixel für Pixel so groß wie vor der Änderung. */
  min-width: 0;
  /* Den Versatz setzt js/seite-start.js beim Scrollen. Ohne Skript und bei
     prefers-reduced-motion bleibt der Rückfallwert 0 stehen — die Fläche
     liegt dann einfach still. */
  transform: translateY(var(--versatz, 0px));
  will-change: transform;
}

.bildflaeche img {
  width: 100%;
  height: 100%;
  /* ⛔ contain, NICHT cover — Begründung oben an .bildflaeche. Bei gleichem
     Verhältnis ist beides dasselbe; unterscheidet sich das Verhältnis, füllt
     `cover` die Fläche und schneidet ab, `contain` zeigt das ganze Bild und
     läßt Rand. Auf dieser Seite stehen Motive, bei denen der Beschnitt genau
     an den Kanten ansetzt, an denen etwas steht.

     Diese Grundregel gilt den Bauarten OHNE eigenes Foto (Platzhalter,
     Grafik). Echte Fotos werden aufs Bildmaß gezogen — siehe
     `.bildflaeche--foto img` gleich darunter. */
  object-fit: contain;
}

/* ⛔ DAS FOTO IST SEIN EIGENER KASTEN — damit die Rundung auf der wirklichen
   Bildkante sitzt (12.08.2026).

   Mit `width/height: 100%` bleibt der Elementkasten die ganze Fläche, und
   `border-radius` rundet diesen Kasten. Wo ein eingepaßtes Bild die Fläche
   nicht ausfüllt, liegt die Rundung dann im leeren Rand und die sichtbaren
   Bildecken bleiben ECKIG — auf der Startseite an `start-drucker.jpg` als
   einzigem Bild im 1:1 deutlich zu sehen und so gemeldet worden.

   ⚠️ EIN ERSTER VERSUCH IST GESCHEITERT, und der Grund gehört hierher: Nur
   `max-width/max-height: 100%` mit `width/height: auto` zu setzen, reicht
   NICHT. Gemessen wurde das 1:1-Bild dann 752 × 752 statt 752 × 501 — der
   Prozentwert in `max-height` fand keinen definiten Bezug, weil die Höhe der
   Fläche selbst erst aus `aspect-ratio` entsteht. Das Element überragte die
   Fläche, `overflow: hidden` schnitt es ab: genau der Beschnitt, der hier
   abgestellt werden soll, nur unbemerkt.

   `position: absolute` löst das: Prozentwerte eines absolut positionierten
   Elements beziehen sich auf den Polsterkasten seines Containers, und der
   ist über `aspect-ratio` definit. `inset: 0` plus `margin: auto` stellt das
   Bild dabei in beiden Achsen mittig — die klassische Zentrierung, die ohne
   Kenntnis der Bildmaße auskommt.

   ⚠️ Nur für `--foto`. Der Platzhalter ist eine Flex-Spalte aus Grafik UND
   Hinweistext; ein absolut liegendes Bild nähme ihm den Fluß und stellte den
   Text an die falsche Stelle. */
.bildflaeche--foto img {
  position: absolute;
  inset: 0;
  margin: auto;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
}

/* ⛔ DER BEZUG DER AUSLAUFBREITE — UND WARUM ER AN `--foto` HÄNGT UND NICHT
   AN `.bildflaeche` (14.08.2026).

   Der Saum muß an allen vier Seiten dieselbe STRECKE sein, sonst ist er oben
   und unten anders dick als links und rechts. Eine Strecke braucht einen
   Bezug, und der einzig richtige ist die Breite der Bildfläche — nicht die
   des Fensters. Gemessen, warum das nicht dasselbe ist:

     Fenster 900px  →  Fläche 334,5px breit   (zwei Spalten, ab 900px)
     Fenster 899px  →  Fläche 732,0px breit   (eine Spalte, darunter)

   Ein Maß in `vw` läge an dieser Stelle um den Faktor 2,2 daneben: Dieselben
   Pixel wären auf der einen Fläche 18 % ihrer Breite und auf der anderen 8 %.
   `cqw` zählt in Prozent DIESER Fläche und trifft deshalb auf jeder
   Fensterbreite denselben Anteil.

   ⚠️ Der Container steht an `--foto` und nicht an `.bildflaeche`, weil nur
   diese Bauart eine Maske trägt. Eine Beschränkung, die keiner braucht, an
   die gemeinsame Klasse zu hängen, verteilt ihre Nebenwirkungen auf drei
   Bauarten, die nichts davon haben — und `.bildflaeche--herz` ist mit seiner
   eigenen `min-height` nachweislich empfindlich gegen Änderungen an der
   Größenrechnung (siehe `min-width: 0` an `.bildflaeche` weiter oben).

   Nachgemessen auf zwölf Breiten von 1920 bis 320: Die Flächenmaße aller
   fünf Bildflächen sind Pixel für Pixel dieselben wie vor der Änderung. */
.bildflaeche--foto { container-type: inline-size; }

/* ⛔ DAS BILD LÄUFT RINGSUM AUS — ÜBER EINE MASKE, NICHT ÜBER EINEN
   AUFGELEGTEN FARBVERLAUF (14.08.2026).

   Vorher saß auf jedem Foto eine 16px-Rundung, und damit eine sichtbare
   Kante: ein Rechteck mit runden Ecken, das gegen die Seitenfläche abschloß.
   Jetzt hört das Bild nicht auf, es geht auf.

   ⛔ EINE MASKE UND KEIN VERLAUF IN DER SEITENFARBE, und das ist der Kern.
   Ein aufgelegter `linear-gradient(transparent, var(--background))` müßte die
   Farbe kennen, in die er läuft. Er ginge im Hellmodus nach Hell und im
   Dunkelmodus nach Dunkel — zwei Werte, die jemand nachziehen muß, und ein
   dritter Fall (Karte, Band, gedruckte Seite), an dem beide falsch sind. Die
   Maske nimmt statt dessen Deckkraft weg. Sie kennt keine Farbe, sondern
   lässt durch, was ohnehin dahinterliegt — und hinter den Themenabschnitten
   liegt nichts als `body`. Deshalb stimmt sie in jedem Schema von selbst.

   ⛔ ZWEI VERLÄUFE, VERRECHNET MIT `intersect`. Eine Maske je Achse; das
   Bild bleibt nur dort stehen, wo BEIDE es stehenlassen. Der Vorgabewert
   wäre `add`, also die Vereinigung — damit bliebe das Bild an den Kanten
   voll stehen und verblaßte allein in den vier Ecken. Genau falsch herum.

   ⚠️ DIE REIHENFOLGE DER BEIDEN COMPOSITE-ZEILEN IST PFLICHT. Ältere
   WebKit-Fassungen kennen nur `-webkit-mask-composite` mit den alten Werten
   (`source-in`), neuere Engines das genormte `mask-composite: intersect`.
   Die genormte Zeile steht deshalb HINTER der alten: Wer beide versteht,
   nimmt die letzte; wer nur die alte kennt, überliest die genormte und
   behält `source-in`. Umgekehrt fiele die moderne Engine auf die
   Alt-Deklaration zurück.

   ⛔ EIN RADIALER VERLAUF WÄRE NICHT DASSELBE. Er läuft von der Mitte nach
   außen und erreicht die Ecken viel früher als die Kantenmitten — auf einer
   3:2-Fläche wäre der Saum an den langen Seiten sichtbar dünner als an den
   kurzen. Verlangt ist ein Saum von gleicher Stärke ringsum, und den geben
   nur zwei achsenparallele Verläufe.

   ⚠️ DIE MASKE SITZT AM BILD, NICHT AN DER FLÄCHE. Sie muß an der wirklichen
   Bildkante ansetzen, und die ist nicht immer die Flächenkante: Die einzige
   1:1-Datei (`start-drucker.jpg`) steht eingepaßt in der 3:2-Fläche und läßt
   links und rechts Rand. Eine Maske an der Fläche verliefe dort im Leeren,
   und die sichtbaren Bildkanten blieben hart — derselbe Fehler wie bei der
   Rundung, die aus genau diesem Grund am 12.08.2026 ans Bild gewandert ist.

   ⚠️ KEINE VERZERRUNG. Eine Maske nimmt Deckkraft, sie skaliert nichts. Das
   Bild behält Kasten und Seitenverhältnis; `max-width/max-height: 100%` mit
   `width/height: auto` bleibt unangetastet. */
/* ⚠️ Der Kopfbereich der Startseite hat sich diese Regel einen halben Tag
   lang geteilt (`.kopf-foto`, 02.09.2026). Seit dort das LOGO statt eines
   Fotos steht, ist er wieder draußen — und das ist kein Versehen, sondern
   der Punkt: Diese Maske löst eine BILDKANTE in den Hintergrund auf. Ein
   Logo hat keine Kante, sondern eine Form; ein Saum ringsum machte daraus
   ein Motiv mit ausgefransten Rändern. */
.bildflaeche--foto img {
  /* Die Auslaufbreite: 8 % der Flächenbreite, nach unten und oben begrenzt.

     Gemessen an den echten Flächenmaßen — die Fläche ist zwischen 288px
     (Fenster 320) und 752px (Fenster 1920) breit:

       Fläche 752 × 501  →  60px Saum  =  8,0 % der Breite, 12,0 % der Höhe
       Fläche 488 × 325  →  39px Saum  =  8,0 % der Breite, 12,0 % der Höhe
       Fläche 288 × 192  →  23px Saum  =  8,0 % der Breite, 12,0 % der Höhe

     Der Anteil bleibt auf jeder Breite derselbe, in mobiler wie in
     Desktop-Ansicht — das ist der Sinn von `cqw`. Die Grenzen fangen nur die
     Extreme ab: unter 22px läse sich der Übergang als Unschärfe statt als
     Absicht, über 64px begänne er auf dem Motiv zu liegen. In der Mitte
     bleiben damit 84 % der Breite und 76 % der Höhe voll deckend stehen —
     genug für das Motiv, das auf allen vier Dateien mittig steht. */
  --weich: clamp(22px, 8cqw, 64px);

  /* Die Stufenfolge EINES Randes, einmal aufgeschrieben und von beiden
     Achsen benutzt — sonst stünde dieselbe Kurve viermal da und liefe beim
     nächsten Nachjustieren auseinander.

     ⚠️ Die Zwischenstufen sind der Grund, warum der Saum nicht als RING
     gelesen wird. Ein glatter Anstieg von 0 auf 1 knickt an seinem inneren
     Ende: Bis dahin wird das Bild heller, danach schlagartig nicht mehr, und
     genau diese Knickstelle sieht das Auge auf ruhigen Bildpartien als feine
     Linie. Die Folge unten steigt am Rand steil an und legt sich nach innen
     flach an die volle Deckkraft an, sodass es keine Knickstelle mehr gibt. */
  --weich-stufen:
    rgba(0, 0, 0, 0)    0,
    rgba(0, 0, 0, .18)  calc(var(--weich) * .32),
    rgba(0, 0, 0, .58)  calc(var(--weich) * .64),
    rgb(0, 0, 0)        var(--weich),
    rgb(0, 0, 0)        calc(100% - var(--weich)),
    rgba(0, 0, 0, .58)  calc(100% - var(--weich) * .64),
    rgba(0, 0, 0, .18)  calc(100% - var(--weich) * .32),
    rgba(0, 0, 0, 0)    100%;

  -webkit-mask-image:
    linear-gradient(to bottom, var(--weich-stufen)),
    linear-gradient(to right,  var(--weich-stufen));
  mask-image:
    linear-gradient(to bottom, var(--weich-stufen)),
    linear-gradient(to right,  var(--weich-stufen));
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}

/* Ausnahme: Dieses Foto steht so da, wie es ist — ohne Auslauf am Rand
   (Material-Navigator, 28.09.2026). */
.bildflaeche--ohne-auslauf img {
  -webkit-mask-image: none;
  mask-image: none;
}

/* Schmalerer Auslauf, halbe Breite der Grundregel (Druckerkompatibilität,
   28.09.2026). Die Maske liest --weich über --weich-stufen neu. */
.bildflaeche--auslauf-schmal img {
  --weich: clamp(11px, 4cqw, 32px);
}

/* Solange die Datei fehlt, trägt die Fläche die Spulen-Grafik: vollständig
   sichtbar statt formatfüllend, ohne eigene Hintergrundfläche und deutlich
   zurückgenommen — sie soll als Platzhalter lesbar sein und nicht mit dem
   Text konkurrieren. */
.bildflaeche--platzhalter {
  background: none;
  /* Der Hinweistext steht unter der Grafik, beide zusammen mittig. */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--abstand-m);
  padding: var(--abstand-2xl);
}
.bildflaeche--platzhalter img {
  object-fit: contain;
  /* Der Innenabstand liegt jetzt an der Fläche, damit er auch für den Text
     darunter gilt — sonst stünde der Hinweis an der Kante. */
  padding: 0;
  width: min(45%, 180px);
  height: auto;
  opacity: 0.55;
}

/* Was hier einmal stehen soll — die Vorlage nennt für mehrere Abschnitte
   „Datei folgt später". Der Hinweis macht die Fläche als ABSICHT lesbar
   statt als vergessenes Bild; er verschwindet mit der ersten echten Datei. */
.bildflaeche-hinweis {
  font-size: var(--label-medium);
  color: var(--on-surface-variant);
  text-align: center;
}

/* --- Merkzettel: großes, sanft pulsierendes Herz ---
   Dasselbe Symbol wie die Merken-Herzen an den Kacheln, nur groß und in der
   Akzentfarbe. Es steht anstelle eines Bildes: Die Geste, um die es in
   diesem Abschnitt geht, kann ein Foto nicht zeigen. */
.bildflaeche--herz {
  background: none;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 240px;
}
.start-herz {
  display: inline-flex;
  color: var(--tertiary);
  animation: pd-herz-puls 1.4s ease-in-out infinite;
}
.start-herz svg {
  width: min(45%, 160px);
  min-width: 120px;
  height: auto;
}

/* ⛔ KRÄFTIGER UND SCHNELLER seit dem 12.08.2026. Vorher: 4 % Größenspiel
   über 2,4s, begründet mit „Ein Herz, das kräftig schlägt, zöge den Blick
   dauerhaft auf einen Nebenweg der Seite". Auf Entscheidung vom 12.08.2026
   ist genau das gewollt — der Merkzettel ist der einzige Abschnitt ohne Foto,
   und das Herz muss die Fläche allein tragen.

   Jetzt 12 % Größenspiel über 1,4s: gut dreimal so viel Ausschlag bei rund
   60 % der Taktlänge. Der Schlag hat außerdem eine FORM statt eines
   gleichmäßigen Hin und Her — zwei Spitzen wie ein echter Herzschlag, dazwischen
   eine kurze Rückfederung und danach eine Pause. Die zweite Spitze bleibt
   kleiner als die erste; ein symmetrisches Doppelpochen wirkt mechanisch.

   ⚠️ Animiert wird ausschließlich `transform` — kein Layout, kein Reflow,
   und die Fläche darunter bleibt in Ruhe. Ein Größenspiel über width/height
   würde in jedem Bild den Abschnitt neu umbrechen. */
@keyframes pd-herz-puls {
  0%   { transform: scale(1); }
  14%  { transform: scale(1.12); }
  28%  { transform: scale(1.02); }
  42%  { transform: scale(1.07); }
  62%  { transform: scale(1); }
  100% { transform: scale(1); }
}

/* ⚠️ Bei Bewegungsreduzierung pulsiert NICHTS — dieselbe Regel wie bei der
   rollenden Spule. Das Herz bleibt dabei sichtbar und in Ruhegröße stehen;
   `animation: none` friert den Grundzustand ein, nicht das erste Bild. */
@media (prefers-reduced-motion: reduce) {
  .start-herz { animation: none; }
}

/* ⛔ DAS BILD FÜLLT SEINE SPALTE (12.08.2026) — vorher stand hier
   `max-width: 520px; margin-inline: auto`.

   Die frühere Begründung lautete: „In einer 700px breiten Spalte wäre ein
   Quadrat 700px hoch und beherrschte den Abschnitt." Sie galt QUADRATISCHEN
   Fotos. Seit alle Bilder im 4:3 stehen, ist dieselbe Spalte nur noch drei
   Viertel so hoch — der Grund ist mit dem Format entfallen.

   Entscheidend ist aber etwas anderes: `margin-inline: auto` zentrierte das
   Bild in seiner Rasterzelle. Damit lag seine Kante bei jedem Abschnitt
   woanders, je nachdem wie breit die Zelle gerade war, und keine zwei
   Abschnitte standen in einer Flucht. Gefüllt liegt die Bildkante immer auf
   der Zellkante — und die Zellen sind gleich breit. */
.bildflaeche--thema {
  width: 100%;
}

/* Für den Kopfbereich gibt es keine Bildfläche mehr — das Foto liegt dort als
   Hintergrund (.kopf-foto weiter oben). Die Bildflächen tragen jetzt
   ausschließlich die Themenabschnitte. */

/* --- Ein Thema je Abschnitt --- */

.start-thema {
  padding: var(--abstand-2xl) 0;
}

/* Zwei Spalten, deren Reihenfolge sich von Abschnitt zu Abschnitt dreht.
   Umgesetzt über `order` und nicht über die Reihenfolge im Markup: Der Text
   steht dadurch IMMER zuerst im Dokument — für Screenreader und für die
   einspaltige Ansicht, in der das Bild unter den Text rutscht. */
.thema-raster {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--abstand-xl);
  align-items: center;
}

.thema-text-spalte { max-width: var(--lesebreite); }

/* Die Knöpfe eines Abschnitts. Regelfall ist einer; der gemeinsame
   Preisabschnitt trägt zwei (Filamente / Harze), weil Kilo- und Literpreise
   nie in einer gemeinsamen Liste stehen. Umbruch erlaubt, damit das zweite
   Ziel auf schmalen Fenstern unter das erste rutscht statt hinauszulaufen. */
.thema-aktionen {
  display: flex;
  flex-wrap: wrap;
  gap: var(--abstand-s);
}

@media (min-width: 900px) {
  .thema-raster {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--abstand-3xl);
  }
  .start-thema--gespiegelt .thema-text-spalte { order: 2; }
  /* Die zweite Spalte ist nicht zwingend ein Bild — der Abschnitt zur
     Wissensbasis trägt dort Materialkacheln. Beide müssen sich drehen, sonst
     bräche der Links-rechts-Wechsel genau an dieser Stelle ab. */
  .start-thema--gespiegelt .bildflaeche,
  .start-thema--gespiegelt .thema-materialien { order: 1; }

  /* ⛔ DIE FLUCHT (12.08.2026). Der Text ist auf --lesebreite begrenzt und
     damit schmaler als seine Rasterzelle (586,5 gegen 752px bei 1920er
     Fenster). Ohne diese Regel steht er IMMER an der linken Zellkante —
     im gespiegelten Abschnitt also mitten in der Seite, während das Bild
     gegenüber bündig an der Außenkante sitzt. Die äußeren Kanten lägen dann
     je nach Abschnitt 165px auseinander.

     `margin-left: auto` schiebt den Textblock an die rechte Zellkante. Damit
     gilt in JEDEM Abschnitt: Was in Spalte 1 steht, beginnt auf der linken
     Außenlinie; was in Spalte 2 steht, endet auf der rechten. Gleich, ob dort
     Text, Foto oder Materialkacheln stehen. */
  .start-thema--gespiegelt .thema-text-spalte { margin-left: auto; }
}

/* --- Materialkacheln als zweite Spalte (Abschnitt „Filament-Wissen") ---

   Sie stehen dort, wo die übrigen Abschnitte ihr Foto haben, und tun etwas,
   was ein Foto hier nicht kann: Sie zeigen die Kennwerte selbst. PLA neben
   ABS beantwortet die Frage, wozu es eine Wissensbasis gibt, ohne einen
   weiteren Satz.

   Dasselbe Breitenmaß wie .bildflaeche--thema: Die Spalte soll ihre Breite
   nicht danach ändern, ob dort ein Foto oder Kacheln stehen. Seit dem
   12.08.2026 heißt das „volle Zellbreite" — die frühere gemeinsame Grenze von
   520px mit `margin-inline: auto` hat beide aus der Flucht genommen. */
.thema-materialien {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--abstand-m);
  width: 100%;
}

/* Kleine Zeile über der Überschrift: sagt, worum es geht, ohne der
   Überschrift die Größe streitig zu machen. */
.thema-marke {
  font-size: var(--label-medium);
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--primary);
  margin-bottom: var(--abstand-s);
}

.start-thema h2 {
  font-size: var(--headline-small);
  line-height: var(--zeile-eng);
}

.thema-text {
  margin-top: var(--abstand-l);
  font-size: var(--body-large);
  line-height: var(--zeile-text);
  color: var(--on-surface-variant);
}

.start-thema .knopf { margin-top: var(--abstand-xl); }

/* Der Materialeinstieg (.start-materialien, .materialraster,
   .materialflaeche und ihre Felder) ist am 07.08.2026 mit dem Abschnitt
   „Du weißt schon, was du drucken willst“ entfallen — samt Raster, und
   damit ohne Reste im Stylesheet. */

/* --- Weiches Einblenden beim Erreichen (ansichtsübergreifend) ---
   Die Klassen setzt js/seite-start.js, und zwar NUR für Abschnitte, die beim
   Aufbau unterhalb des Sichtfensters liegen. Ohne Skript bleibt die Seite
   damit vollständig sichtbar — ein Abschnitt, den niemand mehr einblendet,
   wäre sonst dauerhaft weg.

   Einmalig: Nach dem Einblenden beendet der Beobachter die Beobachtung, die
   Klasse bleibt stehen. Zurückscrollen zeigt den Abschnitt unverändert, es
   blendet nichts ein zweites Mal. */
.einblenden--verborgen {
  opacity: 0;
  transform: translateY(var(--abstand-l));
}
.einblenden--sichtbar {
  opacity: 1;
  transform: none;
  transition: opacity var(--dauer-mittel) ease-out,
              transform var(--dauer-mittel) ease-out;
}

/* ---------- Angebotsliste (Route „/filamente") ---------- */

/* Die Werkzeugzeile über der Liste: links Filter, Sortierung und Versand
   (auf /drucker dazu die Bauartwahl), rechts Ansichtswahl und Trefferzahl in
   einer eigenen Hülle — siehe `.listen-kopf-rechts`. */
.listen-kopf {
  display: flex;
  align-items: center;
  gap: var(--abstand-s);
  flex-wrap: wrap;
  /* Werkzeugleiste und Liste sind zwei Abschnitte, nicht zwei Zeilen. */
  margin-bottom: var(--abstand-2xl);

  /* --- Klebt unter der Kopfleiste (07.08.2026), in BEIDEN Kategorien ---
     Suchfeld, Filter, Sortierung und Trefferzahl sind Werkzeuge AN der Liste.
     Wer 40 Kacheln weit gescrollt ist und den Suchbegriff ändern will, sollte
     dafür nicht erst zurückrollen müssen.

     ⚠️ top ist DASSELBE Maß wie bei der Filterspalte daneben
     (--kopf-hoehe + --abstand-l). Zwei verschiedene Werte ließen die beiden
     Kanten um wenige Pixel gegeneinander versetzt stehen — das sieht nach
     einem Fehler aus, obwohl beide für sich richtig gerechnet sind.

     Der eigene Hintergrund ist Pflicht, nicht Zierde: Ohne ihn liefen die
     Kacheln beim Rollen SICHTBAR durch die Zeile hindurch. Aus demselben
     Grund trägt sie oben und unten Innenabstand — er ist der Streifen, in dem
     die Kachel verschwindet, bevor sie die Schrift erreicht.

     z-index 30: über den Kacheln, unter Vergleichsleiste (40), Hauptleiste
     (45), Kopfleiste (50) und allem Modalen (60+). */
  position: sticky;
  top: calc(var(--kopf-hoehe) + var(--abstand-l));
  z-index: 30;
  background: var(--background);
  padding: var(--abstand-s) 0;
}

/*
 * Der Streifen zwischen Kopfleiste und Suchzeile — sonst läuft die Liste
 * SICHTBAR durch die Lücke.
 *
 * Die Lücke ist die Luft, die die Zeile von der Kopfleiste abhält
 * (--abstand-l im sticky-top). Beim Rollen wandert jede Kachel durch sie
 * hindurch: unten von der Suchzeile verdeckt, oben von der Kopfleiste, und
 * dazwischen 16px lang voll sichtbar. Gemessen: Produktname samt Herz und
 * Vergleichssymbol standen frei zwischen den beiden Leisten.
 *
 * ⚠️ Als PSEUDOELEMENT und nicht über einen kleineren top-Wert: Die Zeile
 * soll auf derselben Höhe kleben wie die Filterspalte daneben. Und nicht über
 * negativen Außenabstand — der kollabiert mit dem Elternelement und zöge die
 * ganze rechte Spalte 16px nach oben, also genau die Ausrichtung wieder
 * kaputt, um die es hier geht.
 */
.listen-kopf::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: var(--abstand-l);
  background: var(--background);
}

.suchfeld {
  display: flex;
  align-items: center;
  gap: var(--abstand-s);
  background: var(--surface-container-high);
  /* ⛔ Eigene Kante, kräftiger als --feld-kante (11.08.2026). Das Suchfeld
     ist das einzige Eingabefeld der Seite, das FREI auf der Seitenfläche
     steht statt in einer Karte — und es hat als Fläche nur 1,29:1 hell und
     1,24:1 dunkel gegen diesen Grund. Für ein Bedienelement fordert WCAG
     1.4.11 3:1; getragen hat das bis dahin gar nichts: hell die 1,70:1 der
     Haarlinie, dunkel überhaupt keine Kante (--feld-kante steht dort auf
     `none`, weil im Dunkelmodus sonst die Flächenstufe reicht — bei diesem
     Feld tut sie es nicht).
     Mit --outline: 4,06:1 hell, 6,03:1 dunkel gegen den Seitengrund.
     Dieselbe Rolle und dieselbe Stärke wie der Rand der Chips daneben, damit
     Suchzeile und Filterchips als eine Familie zu lesen sind. */
  box-shadow: inset 0 0 0 1px var(--outline);
  border-radius: var(--radius-pille);
  padding: 0 var(--abstand-l);
  /* Wächst in den freien Platz. Die Basis ist der Platz, den das Feld für
     seinen eigenen PLATZHALTERTEXT braucht — 190px für „Marke, Material oder
     Farbe" + 32px Innenabstand der Pille + 28px für Lupe und Lücke.

     ⚠️ Das ist kein geschätzter Mindestwert, sondern ein gerechneter, und er
     ist zugleich der Schalter für den Zeilenumbruch. Ein umbrechender
     Flex-Kasten verteilt seine Kinder anhand der flex-basis auf Zeilen und
     schrumpft erst DANACH. Steht die Basis auf dem Bedarf des Platzhalters,
     entscheidet das Umbruchverfahren von selbst richtig: Wo Feld und
     Nachbarn nebeneinander passen, stehen sie nebeneinander; wo nicht,
     bekommt das Feld die ganze Zeile — statt auf eine Breite gedrückt zu
     werden, in der sein Text mitten im Wort endet.

     Vorher standen hier 220px. Bei 320px Fensterbreite blieben dem Text
     dadurch 146px von 190 benötigten, bei 640px 164px — der Platzhalter
     endete in beiden Fällen mitten im Wort.

     Wer den Platzhalter je verlängert, zieht diesen Wert nach. */
  flex: 1 1 252px;
  min-width: 0;
  /* Anker der Vorschlagsliste, die darunter schwebt. */
  position: relative;
}

/* ⛔ DIE TREFFERZAHL TRÄGT SELBST KEIN `margin-left: auto` (18.09.2026).
   Allein geschoben stand sie je nach Fensterbreite mal dicht neben der
   Sortierung, mal einsam hinter leerem Raum. Sie nennt den Umfang der Ansicht
   und gehört deshalb unmittelbar hinter deren Umschalter — auf allen drei
   Listen an derselben Stelle. Nach rechts abgerückt wird die GRUPPE aus
   beiden, und zwar über die Ansichtswahl vor ihr (siehe dort). */
.listen-treffer {
  font-size: var(--body-small);
  color: var(--on-surface-variant);
  white-space: nowrap;
}
.listen-treffer:empty { display: none; }
.suchfeld svg { width: 20px; height: 20px; color: var(--on-surface-variant); flex: 0 0 auto; }

/* Die Lupe löst die Suche aus (der zweite Weg neben Enter) und ist deshalb
   ein echter Knopf. Sie sieht dennoch unverändert aus: Die Fläche kommt vom
   Suchfeld ringsum, der Knopf bringt keine eigene mit.

   Die Innenabstände vergrößern nur die Trefferfläche; die gleich großen
   negativen Außenabstände nehmen sie aus dem Layout wieder heraus, damit das
   Symbol optisch an seiner alten Stelle steht.

   Farbe bleibt --on-surface-variant auf --surface-container-high: 7,7:1 hell,
   8,9:1 dunkel — als Bedienelement weit über den geforderten 3:1. */
.suchfeld-knopf {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  padding: var(--abstand-xs);
  margin-inline: calc(var(--abstand-xs) * -1);
  border: 0;
  background: none;
  border-radius: var(--radius-pille);
  cursor: pointer;
  transition: color var(--dauer-kurz) ease;
}
.suchfeld-knopf:hover svg { color: var(--on-surface); }

/* ⛔ HIER STAND DIE VORSCHLAGSLISTE UNTER DEM SUCHFELD (bis 03.09.2026).

   Sie ist mit dem Suchfeld in die Kopfleiste gewandert und dort neu gebaut:
   Statt einzelner Begriffe (Hersteller, Material, Farbe) schlägt sie
   PRODUKTE vor, gruppiert nach Bereich und mit dem Vergleichspreis rechts.
   Ihre Regeln stehen in css/layout.css bei .kopfsuche-blatt — sie gehören
   jetzt zur Leiste, nicht mehr zu dieser Seite.

   ⚠️ `.suchfeld` selbst bleibt: Die Druckerübersicht führt weiterhin ein
   eigenes Suchfeld in ihrer Werkzeugzeile (js/seite-drucker.js). */
.suchfeld input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  padding: var(--abstand-m) 0;
  outline: none;
  font-size: var(--body-large);
}
.suchfeld input::placeholder { color: var(--on-surface-variant); }

/* flex-wrap UND flex-shrink — beides ist Pflicht, nicht Kür.

   Filter, Sortieren und der Versandschalter brauchen zusammen rund 376px und
   standen bei 390px Ansichtsbreite 2px über dem rechten Rand. Zwei Pixel
   genügen, um das ganze Dokument breiter zu machen als das Fenster, und damit
   für einen waagerechten Scrollbalken auf jeder Listenseite.

   ⚠️ flex-wrap allein half nicht: Mit `flex: 0 0 auto` ist die Zeile als
   Flex-KIND unschrumpfbar und bleibt auf ihrer Maximalbreite — dann gibt es
   nie den Platzmangel, der einen Umbruch auslösen würde. Erst das Schrumpfen
   bringt den Umbruch. */
.filter-zeile {
  display: flex;
  flex-wrap: wrap;
  gap: var(--abstand-s);
  flex: 0 1 auto;
  min-width: 0;
}

/* ⛔ HIER STAND BIS ZUM 18.09.2026 DIE SCHNELLFILTER-PILLENREIHE
   („Alle · PLA · PETG …", .schnellfilter und .schnellchip). Sie ist
   ersatzlos entfallen — auf jeder Breite, nicht nur mobil. Sie war eine
   ZWEITE Bedienung desselben Kriteriums, das im Filterfenster und in der
   Filterspalte ohnehin steht: Der Weg dorthin verschließt sich nicht, es
   verschwindet nur die Abkürzung. Wer die Regeln stehen lässt, beschreibt
   Klassen, die kein Element mehr trägt — und das liest sich beim nächsten
   Mal wie ein vorhandener Baustein. */

/* ---------- Karten oder Tabelle ----------

   Zwei gedrückte Schalter, kein einzelner Umschalter — die Begründung steht
   bei ansichtsumschalterHtml in js/seite-liste.js. */
.ansichtswahl {
  display: inline-flex;
  flex: 0 0 auto;
  gap: var(--abstand-xs);
  padding: 2px;
  border-radius: var(--radius-pille);
  background: var(--surface-container-highest);
  box-shadow: var(--feld-kante);
}

/* ⛔ DIE RECHTE GRUPPE RÜCKT ALS GANZES AB (18.09.2026).

   Die Werkzeugzeile führt links, was die Liste EINSTELLT (Filter, Sortierung,
   Versand — auf /drucker dazu die Bauartwahl), und rechts, was ihr Ergebnis
   BESCHREIBT (Ansichtswahl und Trefferzahl).

   ⛔ SIE IST EIN EIGENES ELEMENT, UND DAS IST GEMESSEN NÖTIG. Mit einem
   bloßen `margin-left: auto` an der Ansichtswahl verschluckte diese den ganzen
   freien Platz ihrer Zeile; die Trefferzahl paßte nicht mehr daneben, rutschte
   allein in die nächste Zeile — und stand dort LINKS. Gemessen auf /filamente
   bei 1100, 800 und 768px und auf /drucker bei 1023 und 768px. Als Hülle
   bricht das Paar geschlossen um und steht auch in der neuen Zeile rechts.

   ⛔ EIN AUTO-RAND UND KEINE `order`-REGEL. Die rechte Gruppe steht auch im
   Markup hinten — `order` stellte allein das Bild um und ließe Tastatur und
   Vorlesehilfe bei der alten Folge, ohne dass am Bildschirm etwas auffällt.

   ⚠️ `flex: 0 0 auto` an Hülle und Ansichtswahl ist Pflicht: Jene trägt
   zwei Beschriftungen („Karten", „Tabelle"), und unter ihre Inhaltsbreite
   gedrückt brächen die Wörter um.

   ⚠️ Auf schmalen Fenstern gilt statt dessen ein Raster (ganz am Ende
   dieser Datei); dort löst sich die Hülle in `display: contents` auf und mit
   ihr der Auto-Rand — die Lage entscheidet dann das Rasterfeld. */
.listen-kopf-rechts {
  display: flex;
  align-items: center;
  gap: var(--abstand-s);
  flex: 0 0 auto;
  margin-left: auto;
}

.ansichtchip {
  display: inline-flex;
  align-items: center;
  gap: var(--abstand-xs);
  min-height: 36px;
  padding: 0 var(--abstand-m);
  border: 0;
  border-radius: var(--radius-pille);
  background: none;
  font-size: var(--label-large);
  font-weight: 500;
  color: var(--on-surface-variant);
  cursor: pointer;
  transition: background-color var(--dauer-kurz) ease, color var(--dauer-kurz) ease;
}

.ansichtchip svg { width: 18px; height: 18px; }
.ansichtchip:hover { background-color: var(--zustand-hover); }

/* Der gewählte Schalter steht auf der Kartenfläche und hebt sich damit aus
   der vertieften Leiste heraus — dieselbe Bewegungsrichtung wie ein Reiter,
   der nach vorn kommt. */
.ansichtchip--aktiv {
  background: var(--surface-container);
  box-shadow: var(--karten-kante);
  color: var(--on-surface);
}
.ansichtchip--aktiv:hover { background-color: var(--surface-container); }

/* ---------- Handy: Trefferzahl neben dem Suchfeld, solange sie hineinpaßt ----

   Die Trefferzahl stand als eigene, vierte Zeile über der Liste — 21px
   Inhalt, die mit Abstand 29px kosteten, für eine Angabe von 74px Breite, die
   in jeder anderen Zeile Platz hat. Sie rückt deshalb neben das Suchfeld.

   ⚠️ Ob sie danebensteht oder darunter, entscheidet die flex-basis des
   Suchfeldes — und die steht an der Grundregel `.suchfeld` auf dem Bedarf des
   Platzhaltertextes (siehe dort). Hier wird sie bewußt NICHT verkleinert:
   Genau das war der gemeldete Fehler. Mit 120px paßte die Trefferzahl zwar
   überall daneben, aber das Feld war bei 320px nur 206px breit, dem Text
   blieben 146px von 190 benötigten, und der Platzhalter endete mitten im
   Wort. Eine Zeile zu sparen ist es nicht wert, das Feld unter seine eigene
   Beschriftung zu drücken.

   Die Reihenfolge steht hier und nicht im Markup, weil sie nur auf dem Handy
   gilt: Ab 640px stehen Suchfeld und Knopfzeile nebeneinander, und die
   Trefferzahl gehört dann ans Ende. */
/* ⛔ HIER STANDEN BIS ZUM 18.09.2026 DREI `order`-REGELN — Suchfeld,
   Trefferzahl und Knopfzeile wurden unter 640px in eine andere Reihenfolge
   gebracht als im Markup. Sie sind ersatzlos entfallen: Die Werkzeugzeile
   trägt auf allen drei Listen und auf jeder Breite dieselbe Abfolge
   (Filter · Sortierung · Versand, dann Ansichtswahl · Trefferzahl), und
   `order` dreht nur das Bild um, nicht die Tastatur- und Vorlesereihenfolge —
   auf schmalen Fenstern liefen beide also auseinander. Das Suchfeld gibt es hier
   ohnehin seit dem 03.09.2026 nicht mehr (es steht in der Kopfleiste).
   ⚠️ Was die Knopfzeile unter 640px in eine eigene Reihe bringt, ist bei den
   Produktlisten das Raster am Ende dieser Datei — und auf /drucker die eine
   Regel gleich hier darunter. */
@media (max-width: 639px) {
  /* ⛔ DIE KNOPFZEILE BEKOMMT AUF /drucker EINE EIGENE REIHE, und das ist
     keine Kosmetik: Unter 640px schrumpfen ihre Knöpfe beim Rollen
     (--werkzeug-roll). Lägen sie im freien Umbruch, entschiede ihre Größe
     über die Zahl der Reihen — gemessen sprang die Zeile bei 560px von 152,4
     auf 102,4px, sobald man zu rollen begann. Mit fester Reihe hängt die
     Reihenzahl allein an der Breite, genau wie beim Raster der Produktlisten.
     ⚠️ Eingegrenzt auf die Listen OHNE Versandschalter: Die Produktlisten
     lösen dasselbe eine Zeile sauberer, nämlich mit benannten Rasterfeldern,
     und dort würde diese Regel das `display: contents` überstimmen. */
  .listen-kopf:not(:has([data-rolle="versand"])) .filter-zeile {
    flex-basis: 100%;
  }

  /* --- Auf dem Handy SCHRUMPFT die Zeile beim Runterscrollen (02.09.2026) ---

     ⛔ Bis dahin WICH sie ganz aus dem Bild: js/app.js schrieb
     data-suchleiste="weg" ans <html>, hier stand translateY(-120%), dazu
     eine Übergangszeit. Auf Entscheidung vom 02.09.2026 verschwindet die
     Suchzeile nie mehr — sie bleibt jederzeit sichtbar und erreichbar.
     Platz gibt sie trotzdem ab: Filter-, Sortier- und Versandknopf werden
     kleiner und rücken zusammen, die Zeile selbst verliert Luft. Das
     Suchfeld bleibt, wie es ist.

     ⛔ FORTLAUFEND, über denselben Fortschritt wie die Kopfleiste. Der Wert
     kommt als --werkzeug-roll (0…1) aus js/app.js in die Empfängerregel
     (css/layout.css, „Empfänger der gemessenen Kopfhöhe") — .listen-kopf
     steht dort, deshalb landet er hier. Und es gibt KEINE transition mehr:
     Das war richtig, solange die Zeile ein Schalter mit zwei Zuständen war.
     Ein Wert, der dem Rollen folgt, darf keine Übergangszeit haben — sie
     liefe einem Ziel hinterher, das sich in jedem Bild weiterbewegt (dieselbe
     Regel wie beim Schrumpfen der Kopfleiste, css/layout.css).

     ⛔ Der Rückfall in var(--werkzeug-roll, 0) ist Pflicht: js/app.js
     schreibt den Wert nur unterhalb von 640px (WERKZEUG_BREITE, dieselbe
     Zahl wie diese Medienabfrage) — ohne Rückfall wären alle calc() hier
     ungültig, und die Knöpfe hätten gar keine Höhe.

     Endlagen (0 → 1): Knopf 40 → 32px hoch, Schrift 14 → 12px, Innenabstand
     16 → 10px, Symbol 20 → 16px, Lücken und Zeilenluft 8 → 4px. Zusammen
     rund 24px, die die Zeile abgibt. 32px bleiben ein bedienbares Ziel
     (WCAG 2.5.8 verlangt 24px). Jeder Summand ist linear im Fortschritt,
     also ist es auch die Zeilenhöhe: Der Inhalt darunter rückt gleichmäßig
     nach, ohne Sprung.

     Bewegungsreduzierung braucht hier keinen eigenen Zweig: js/app.js
     liefert dann nur 0 oder 1 — zwei Zustände, kein Zwischenschritt. */
  .listen-kopf {
    --wz: var(--werkzeug-roll, 0);
    /* ⛔ NUR die Luft oben und unten, NICHT die Lücke zwischen den Kindern.
       Die Lücke entscheidet mit, ob die Trefferzahl neben das Suchfeld
       passt (252px Basis + Lücke + 74px): Schrumpfte sie von 8 auf 4px,
       kippte der Umbruch bei 362–365px Fensterbreite mitten im Rollweg —
       ein Sprung um eine ganze Zeile. Was den Umbruch beeinflusst, hängt
       nicht am Fortschritt. */
    padding-block: calc(var(--abstand-s) - 4px * var(--wz));
  }
  .listen-kopf .filter-zeile {
    gap: calc(var(--abstand-s) - 4px * var(--wz));
  }
  .listen-kopf .filter-zeile .knopf {
    min-height: calc(40px - 8px * var(--wz));
    padding-inline: calc(var(--abstand-l) - 6px * var(--wz));
    font-size: calc(var(--label-large) - 2px * var(--wz));
    gap: calc(var(--abstand-s) - 4px * var(--wz));
  }
  .listen-kopf .filter-zeile .knopf svg {
    width: calc(20px - 4px * var(--wz));
    height: calc(20px - 4px * var(--wz));
  }
}

/* ⛔ DIE ZEILENZAHL DER KNOPFZEILE DARF NICHT AM FORTSCHRITT HÄNGEN.

   Gemessen beim Bauen (02.09.2026), 390px: In voller Größe brauchen Filter,
   „Sortieren ↑" und „Versand einrechnen" rund 400px und brechen in der
   358px breiten Zeile um; geschrumpft brauchen sie rund 340px und passen in
   EINE Zeile. Die Zeilenhöhe lief damit nicht linear, sondern sprang mitten
   im Rollweg um eine Knopfzeile: Endlagen 150 und 105px, in der Mitte 145,6
   statt 127,6. Genau der Sprung, den es nicht geben darf — und einer, den
   das Schrumpfen selbst erzeugt, weil kleinere Knöpfe irgendwann passen.

   Deshalb unter 480px ein RASTER mit fester Spaltenzahl statt flex-wrap:
   Zwei Spalten in Inhaltsbreite, der dritte Knopf rückt in die zweite
   Reihe. Wie viele Reihen es gibt, hängt damit allein an der Zahl der
   Knöpfe — nie an ihrer Breite und nie am Fortschritt. Für die drei Knöpfe
   der Produktlisten ist das auf jedem Handy dasselbe Bild wie bisher in
   Ruhe (Filter und Sortieren oben, Versand darunter); es bleibt nur beim
   Rollen so.

   Warum 479px und nicht 639px: Ab 480px stehen der Zeile mindestens 448px
   zur Verfügung, dort passen die drei Knöpfe schon in voller Größe in eine
   Zeile — und was in voller Größe passt, passt geschrumpft erst recht. Ein
   Umbruch, der in beiden Endlagen gleich ausfällt, kann nicht springen.

   ⚠️ Die Druckerseite trägt nur zwei Knöpfe, aber lange Sortiernamen
   („Feinste Auflösung zuerst", rund 210px). Neben „Filter" sind das 314px —
   unter 360px zu viel für eine Zeile, und mit zwei festen Spalten liefe die
   Zeile über den Rand. Ihr Sortierknopf bekommt dort die ganze Reihe, also
   zwei Reihen in jeder Lage: derselbe Grundsatz, nur mit anderer Zahl. */
@media (max-width: 479px) {
  .listen-kopf .filter-zeile {
    display: grid;
    grid-template-columns: repeat(2, max-content);
    justify-content: start;
  }
}
@media (max-width: 359px) {
  .listen-kopf .filter-zeile > [data-rolle="drsortieren"] {
    grid-column: 1 / -1;
  }
}

/* ⛔ HIER STAND BIS ZUM 18.09.2026 EINE REGEL FÜR
   `.listen-kopf .filter-zeile > .ansichtswahl`. Sie ist gegenstandslos: Die
   Ansichtswahl liegt seither NICHT mehr in der Knopfzeile, sondern steht als
   eigenes Kind der Werkzeugzeile hinter ihr und rückt über einen Auto-Rand
   nach rechts ab. Wer sie wieder in die Knopfzeile stellt, bricht die
   Abfolge, die alle drei Listen teilen — und käme von dort auf schmalen
   Fenstern nur noch über eine Regel heraus, die das Bild umstellt und die
   Tabreihenfolge stehen lässt. */

/* ---------- Abstände INNERHALB eines Filterkriteriums (10.08.2026) ----------

   ⛔ Bis dahin standen Kopfzeile, Suchfeld und Chips mit NULL Pixel Abstand
   aufeinander. Gemessen auf /drucker im Ruhezustand:

       input.filter-suchfeld  ->  0,0px  ->  div.chip-reihe
       div.krit-kopf          ->  0,0px  ->  div.chip-reihe
       div.krit-kopf          ->  0,0px  ->  div.regler

   Null ist keine Überlappung im Sinne der Geometrie — und genau deshalb ist es
   lange durchgerutscht: Ein Prüffall, der auf NEGATIVE Abstände sieht, meldet
   hier „in Ordnung". Sichtbar ist es trotzdem eine: Ein umrandetes Eingabefeld
   und ein umrandeter Chip, die sich Kante an Kante berühren, sehen aus, als
   läge das eine auf dem anderen. So ist es am 10.08.2026 auch gemeldet worden.

   Verstärkt wurde der Eindruck dadurch, dass die GEFILTERTE Trefferreihe ihre
   8px hatte (.filter-suchtreffer) und die ruhende Schnellauswahl nicht: Beim
   Tippen sprang der Abstand auf, beim Leeren wieder zu.

   Die Regel steht am Abschnitt und nicht an jedem Baustein einzeln, damit ein
   neues Kriterium seinen Abstand mitbekommt, ohne dass jemand daran denken
   muss. Sie gilt nur hier: .sheet-abschnitt gibt es ausschließlich in den
   beiden Filterformularen. */
.sheet-abschnitt > * + * { margin-top: var(--abstand-s); }

/* Die Skala gehört zu IHREM Regler und bleibt dicht darunter — sie ist seine
   Beschriftung, kein eigener Baustein. Höhere Spezifität als die Regel oben,
   damit die Reihenfolge im Stylesheet nicht darüber entscheidet. */
.sheet-abschnitt > .regler-skala { margin-top: var(--abstand-xxs); }

/* Filterformular-Bausteine (identisch in Bottom-Sheet und Desktop-Sidebar). */
.filter-suchfeld {
  width: 100%;
  margin-top: var(--abstand-s);
  padding: var(--abstand-s) var(--abstand-m);
  border: 1px solid var(--outline);
  border-radius: var(--radius-s);
  background: var(--surface-container-lowest);
  color: inherit;
  transition: border-color var(--dauer-kurz) ease;
}
.filter-suchtreffer { margin-top: var(--abstand-s); }

.temperatur-kopf {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.temperatur-kopf h3 { margin: 0; }
/* .temperatur-wert ist am 07.08.2026 entfallen: Die Wertanzeige der Regler
   lebt in der Blase am Griff (.regler-wert), eine zweite im Kopf lief ihr
   zwangsläufig hinterher. */
.temperatur-aufheben { align-self: flex-end; }

/* Sortier-Sheet: eigene Radiooptionen (auch der deaktivierte Platzhalter). */
.sortier-option {
  display: flex;
  gap: var(--abstand-s);
  align-items: center;
  width: 100%;
  padding: var(--abstand-s) 0;
  border: 0;
  background: none;
  cursor: pointer;
  text-align: left;
  font-size: var(--body-large);
  /* Die Ordnerwahl zeigt NAMEN, also Freitext des Nutzers. Ein Flex-Kind
     wird von sich aus nicht schmaler als sein längstes Wort; ohne diese
     beiden Zeilen sprengte ein Name ohne Leerzeichen das Sheet. Dasselbe
     Muster wie bei der Ordnerkachel — dort steht die Begründung ausführlich. */
  min-width: 0;
  overflow-wrap: anywhere;
}
.sortier-radio {
  width: 20px; height: 20px;
  border: 2px solid var(--outline);
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
}
.sortier-option[aria-checked="true"] .sortier-radio { border-color: var(--primary); }
.sortier-radio::after {
  content: "";
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--primary);
  transform: scale(0);
  transition: transform var(--dauer-kurz) ease;
}
.sortier-option[aria-checked="true"] .sortier-radio::after { transform: scale(1); }
/* ⚠️ 0,72 statt der früheren 0,38 (10.08.2026). 0,38 ist der Wert für ECHTE
   Sperren — und genau das sind diese Einträge nicht: js/seite-liste.js hält
   ausdrücklich fest, dass sie klickbar und tastaturerreichbar bleiben, damit
   sie ihren Grund nennen können. Der Widerspruch stand im Quelltext selbst:
   Der Kommentar wollte Erreichbarkeit, die Deckkraft nahm die Lesbarkeit.
   Gemessen mit 0,38: Hauptzeile 2,38:1, Unterzeile 2,02:1 — beide weit unter
   den 4,5:1 für Normaltext. Die Unterzeile war die schlechtere, obwohl sie
   die eigentliche Auskunft trägt: Sie erbt die Deckkraft des Knopfes UND
   setzt zusätzlich --on-surface-variant, die beiden Dämpfungen multiplizieren
   sich. Gedämpft bleiben die Einträge trotzdem sichtbar, und die Sperre trägt
   mit dem Zeiger und dem Hinweistext zwei nicht-farbliche Merkmale. */
.sortier-option--platzhalter { opacity: 0.72; cursor: default; }
.sortier-option--platzhalter .sortier-radio { border-color: currentColor; }
.sortier-option--platzhalter small { color: var(--on-surface-variant); }

/* Desktop: dauerhafte Filterleiste links neben der Liste. */
.liste-layout { display: block; }
.filter-sidebar { display: none; }
/* Der Ziehbereich für die Spaltenbreite gehört zur Desktop-Fassung: Unter
   1024px liegt die Leiste gar nicht seitlich, dort gibt es keine Breite zu
   ziehen. Die Regel steht hier oben, damit sie auch für Kategorien gilt, die
   den Griff mitzeichnen, aber nie in die Medienabfrage kommen. */
.fs-griff { display: none; }

/* Eingeklappt wandert der Kopf der Filterspalte in die WERKZEUGZEILE
   (js/seite-liste.js, 18.09.2026). Unter 1024px liegt die Spalte gar nicht
   seitlich — dort darf die Pille nicht erscheinen, auch wenn der gespeicherte
   Zustand „zu" ist und der Knoten deshalb schon in der Zeile steht. */
.listen-kopf > .fs-kopf { display: none; }

@media (min-width: 1024px) {
  .liste-layout {
    display: grid;
    /* minmax(0, 1fr) statt 1fr: sonst kann die Liste die Spalte über die
       Fensterbreite hinaus aufdrücken (Grid-Mindestbreite = auto).

       420 statt der früheren 280px (07.08.2026): Die Spalte ist bewusst
       deutlich breiter, damit Kriterien nebeneinander stehen können — die
       Chips einer Reihe brechen seltener um, und die Schalter stehen zu
       zweit in einer Zeile (Raster unten).

       ⚠️ Seit dem 20.08.2026 ist das nur noch der STARTWERT: Die Spalte
       lässt sich am Griff ziehen, und js/seite-liste.js schreibt die
       gespeicherte Breite als eigene Deklaration an dieses Element. Die
       Zahl hier gilt, solange kein Skript gelaufen ist — sie muss deshalb
       zu PD.zustand.FILTER_BREITE_STANDARD passen. */
    grid-template-columns: 420px minmax(0, 1fr);
    gap: var(--abstand-l);
    align-items: start;
  }

  /* ⛔ ALLE DREI Kinder stehen ausdrücklich in ihrer Zelle, seit der Griff
     als drittes dazugekommen ist. Automatische Platzierung füllt Zelle für
     Zelle: Der Griff belegte sonst 1/2, und die Liste rutschte in eine
     zweite Zeile UNTER die Filterspalte. Wer ein viertes Kind einhängt,
     gibt ihm ebenfalls einen Platz. */
  .liste-layout > .filter-sidebar { grid-area: 1 / 1; }
  .liste-layout > .liste-spalte { grid-area: 1 / 2; }

  /* ---------- Griff: die Spaltenbreite ziehen (20.08.2026) ----------
     Er sitzt in der Lücke zwischen Spalte und Liste — dort ist nichts
     anderes, und er verdeckt damit weder den Rollbalken der Spalte (der
     liegt innen an ihrer rechten Kante) noch Inhalt der Liste.

     ⚠️ Er ist ein RASTERKIND in Spalte 1 mit negativem Außenabstand, KEIN
     absolut positioniertes Element: So folgt er der Spaltenkante von selbst,
     auch wenn das Fenster schmaler wird und die Spalte gedeckelt wird. Ein
     absolut gesetztes `left` müsste bei jeder Fensteränderung nachgerechnet
     werden — und stünde nach jedem vergessenen Nachziehen still neben der
     Kante.

     `align-self: stretch` gegen das `align-items: start` der Zeile: Der
     Griff soll über die ganze Höhe fassbar sein, nicht nur oben. */
  .liste-layout > .fs-griff {
    display: block;
    grid-area: 1 / 1;
    justify-self: end;
    align-self: stretch;
    width: var(--abstand-l);
    margin-right: calc(-1 * var(--abstand-l));
    padding: 0;
    border: 0;
    background: none;
    cursor: col-resize;
    /* Berührung soll die Seite weiterhin rollen können — gezogen wird hier
       nur mit einem feinen Zeigegerät (der Zuhörer prüft pointerType). */
    touch-action: auto;
  }

  /* Die Linie erscheint erst, wenn der Griff gemeint ist. Dauerhaft sichtbar
     wäre sie eine seitenlange Kante mitten im Layout, die nichts trennt —
     die Spalte hat ihre eigene Fläche und braucht keinen Strich daneben. */
  .liste-layout > .fs-griff::before {
    content: "";
    display: block;
    width: 3px;
    height: 100%;
    margin: 0 auto;
    border-radius: 2px;
    background: var(--primary);
    opacity: 0;
    transition: opacity var(--dauer-kurz) ease;
  }
  .liste-layout > .fs-griff:hover::before,
  .liste-layout > .fs-griff:focus-visible::before,
  .liste-layout--zieht > .fs-griff::before { opacity: 1; }
  /* Der Strich IST die Fokusmarke — eine Umrandung um ein seitenhohes
     Element umrahmte die halbe Seite. --primary steht auf der Seitenfläche
     bei 6,1:1 und damit weit über den 3:1 für grafische Marken. */
  .liste-layout > .fs-griff:focus-visible { outline: none; }
  /* Eingeklappt gibt es keine Breite zu ziehen — dort führt der bekannte
     Knopf zurück. */
  .liste-layout--filter-zu > .fs-griff { display: none; }

  /* Während des Ziehens: Der Zeiger behält seine Bedeutung auch über der
     Liste, und nichts wird beim Wischen markiert. EINMAL beim Anfassen
     gesetzt und einmal beim Loslassen genommen — nicht je Bild. */
  html.zieht-spalte, html.zieht-spalte * {
    cursor: col-resize !important;
    user-select: none !important;
  }

  /* ⛔ EINGEKLAPPT GIBT DIE SPALTE IHREN PLATZ GANZ AB (18.09.2026).
     Die Liste läuft über die volle Inhaltsbreite — bündig mit der
     Werkzeugzeile darüber —, und der Kopf der Spalte steht als Pille IN
     dieser Zeile (js/seite-liste.js verschiebt den Knoten dorthin).
     Bis dahin blieb hier ein 64px-Streifen stehen: Die Liste verlor seine
     Breite dauerhaft, und er trug ein Symbol ohne Beschriftung. */
  .liste-layout--filter-zu { grid-template-columns: minmax(0, 1fr); }
  /* ⛔ Die Grundregel setzt die Liste in Spalte 2 — die gibt es eingeklappt
     nicht mehr, und eine Rasterangabe auf eine fehlende Spur legt still eine
     implizite an: Die Liste stünde rechts neben einer leeren Spalte. */
  .liste-layout--filter-zu > .liste-spalte { grid-area: 1 / 1; }

  /* Eigene Scrollfläche, unabhängig von der Ergebnisliste.
     ⚠️ Bewusste Umkehr einer früheren Entscheidung: Hier stand „KEIN
     max-height und KEIN overflow, verschachteltes Scrollen war das
     App-Muster". Das hat am Desktop nicht getragen. Die Leiste ist im
     Erweitert-Modus höher als jedes normale Fenster, und die Folge war in
     beiden bisherigen Zuständen dieselbe Sackgasse: klebend blieb ihr unteres
     Ende unerreichbar, mitlaufend musste man erst die halbe Ergebnisliste
     wegscrollen, um an die letzten Filter zu kommen — und hatte dann die
     Treffer nicht mehr im Blick, auf die der Filter ja wirkt.
     Beide sind jetzt getrennt: die Leiste scrollt in sich, die Liste daneben.
     Unter 1024px bleibt es beim Bottom-Sheet, dort gibt es die Leiste gar
     nicht. */
  .filter-sidebar {
    display: block;
    background: var(--surface-container);
    border-radius: var(--radius-m);
    /* OBEN UND UNTEN kein eigener Innenabstand: Beide Kanten tragen klebende
       Zeilen (Kopf mit dem Auf-/Zuklappen, Fuß mit Zurücksetzen/Anwenden),
       und ein Polster an der Spalte läge AUSSERHALB von deren Klebeflächen —
       der Inhalt liefe im Polsterstreifen sichtbar an ihnen vorbei. Kopf und
       Fuß bringen die Luft deshalb als eigenen Innenabstand mit.

       ⛔ 1px RINGSUM — ES HÄLT DIE KANTE FREI (23.09.2026). Dieselbe Falle
       wie bei `.tabelle-rahmen` (komponenten.css) und aus demselben Grund:
       --karten-kante ist ein inset-Schatten, er liegt über der Fläche, aber
       UNTER den Kindern. Der klebende Kopf greift mit -32px Außenabstand
       bis an die Kante hinaus und deckte damit die obere Kante ganz und die
       Seiten auf seiner Höhe — gemessen fehlte die linke Linie von y 294 bis
       397, also genau über den Kopf hinweg. Die Spalte stand oben links ohne
       Umrandung da, während sie sie unten trug; theme.css hielt das als
       bekannten Preis fest.

       Das Polster schiebt Kopf und Fuß um genau die Strichstärke nach innen:
       Ihr Außenabstand bleibt -32px, der seitliche Innenabstand wächst um
       dieselbe 1px. Die Fläche deckt damit weiterhin alles ab, was im
       Polsterstreifen an ihr vorbeiliefe — nur nicht mehr die Linie selbst. */
    padding: 1px calc(var(--abstand-xl) + 1px);

    position: sticky;
    top: calc(var(--kopf-hoehe) + var(--abstand-l));
    /* Fensterhöhe minus Kopfzeile minus je einmal Luft oben und unten. Die
       frühere Sonderregel „erst ab 900px Fensterhöhe kleben" entfällt damit:
       Sie gab es nur, weil die Leiste höher werden konnte als das Fenster.
       Das kann sie jetzt nicht mehr. */
    max-height: calc(100vh - var(--kopf-hoehe) - var(--abstand-l) * 2);
    /* dvh für Browser, die es kennen: auf Tablets im Querformat wandert die
       100vh-Grenze mit ein- und ausfahrender Adressleiste. */
    max-height: calc(100dvh - var(--kopf-hoehe) - var(--abstand-l) * 2);
    overflow-y: auto;
    /* ⚠️ Waagerecht HIDDEN, nicht dem auto der Senkrechten überlassen:
       overflow-y:auto macht auch die Querrichtung zu auto, und die
       Tooltip-Blasen der (i)-Symbole zählen — unsichtbar, aber raumgreifend —
       zum Scrollbereich. Ergebnis war ein horizontaler Rollbalken unter der
       Spalte, der nirgendwohin führte. */
    overflow-x: hidden;
    /* ⛔ AM ENDE DER SPALTE ROLLT DIE SEITE WEITER (04.09.2026).
       Hier stand `contain` mit der Begründung, die letzte Radumdrehung über
       dem Filter solle die Ergebnisliste nicht wegschieben. Auf Ansage
       umgedreht — und die alte Fassung war schlechter, als ihre Begründung
       klingt: `contain` gilt für JEDEN Rollbehälter, auch für einen, der
       nichts zu rollen hat. Gemessen trägt die Spalte bei 1440×900 genau
       392px Inhalt in 392px Höhe — sie ist gar nicht rollbar, und trotzdem
       verschluckte sie jede Radumdrehung. Wer den Zeiger über dem Filter
       hält, stand damit vor einer Seite, die sich nicht bewegt.
       `auto` reicht die Bewegung erst weiter, wenn die Spalte selbst am Ende
       ist — genau die Reihenfolge, die gefordert ist. */
    overscroll-behavior: auto;
    /* Platz für den Rollbalken dauerhaft freihalten: Der Wechsel zwischen
       Einfach und Erweitert ändert die Höhe des Formulars stark, und ohne
       diese Reserve rückte der Inhalt bei jedem Wechsel seitlich. */
    scrollbar-gutter: stable;
    /* Bezugsrahmen für die Abfrage weiter unten: Wie das Formular sich
       aufteilt, hängt an der Breite DIESER SPALTE, nicht am Fenster — seit
       sie sich ziehen lässt, sind das zwei verschiedene Größen. Dieselbe
       Bauart wie .dr-liste auf der Druckerseite. */
    container-type: inline-size;
  }

  /* ⛔ HIER STAND `.filter-sidebar .sheet-fuss` — der klebende Fuß mit
     „Zurücksetzen / Anwenden" (bis 23.09.2026). Keine Spalte trägt ihn mehr:
     Die Produktspalten wirkten schon immer sofort, die Druckerspalte seit
     heute (js/seite-drucker.js). Eine Regel, die eine Klasse beschreibt, die
     kein Element mehr trägt, liest sich beim nächsten Mal wie ein vorhandener
     Baustein — deshalb ist sie weg und nicht auskommentiert.

     ⚠️ `.sheet-fuss` selbst BLEIBT (komponenten.css): Das eingebettete
     Formular im Filterfenster trägt ihn weiter, und dort hat „Anwenden"
     seinen Grund — im Fenster stehen die Regler. */

  /* ⛔ DIE SPALTE DER DRUCKERSEITE ROLLT NICHT (23.09.2026). Sie führt drei
     Gruppen mit je drei Einträgen und einen Knopf; ein Rollbehälter, der
     nichts zu rollen hat, kostet nur den Streifen, auf dem sein Balken säße
     (scrollbar-gutter: stable hält ihn dauerhaft frei).

     ⚠️ DIE PRODUKTSPALTEN BEHALTEN BEIDES, und das ist kein Versehen: Farbe,
     Mengenregler, Shop und Weitere wachsen mit dem Bestand, und eine Spalte
     ohne eigene Höhengrenze klebte als `position: sticky` mit ihrem unteren
     Ende dauerhaft unter dem Sichtfenster — der Knopf „Erweiterte Filter"
     wäre dann nicht mehr erreichbar. Genau das ist auch hier der Preis, wenn
     jemand die Herstellergruppe über „Mehr anzeigen" auf 19 Einträge
     aufklappt und das Fenster niedrig ist. */
  .filter-sidebar--kurz {
    max-height: none;
    overflow: visible;
    scrollbar-gutter: auto;
  }

  /* Der Filter-Button der mobilen Ansicht entfällt am Desktop. */
  .filter-zeile .nur-mobil { display: none; }

  /* ---------- Kopf der Filterspalte: Auf- und Zuklappen ---------- */

  /* ⛔ SEIT DEM 03.09.2026 KLEBT DIE ZEILE, NICHT DER SCHALTER. Sie trägt
     seit dem 23.09.2026 DREI Teile: die Überschrift als reinen Text, daneben
     den Auf-/Zuklapper und darunter „Alle Filter zurücksetzen" über die
     volle Breite. Ein Knopf im Knopf wäre ungültiges Markup; also klebt der
     Wrapper, und die Knöpfe sind gewöhnliche Fluss-Kinder.
     Die Fläche deckt den darunter durchlaufenden Inhalt ab; die Luft zur
     Spaltenkante bringt der Kopf als EIGENEN Innenabstand mit (die Spalte
     selbst hat oben keinen — siehe .filter-sidebar). */
  .fs-kopf {
    display: flex;
    /* ⛔ DER UMBRUCH IST DIE ZWEITE ZEILE. Der Reset nimmt über
       `flex-basis: 100%` die ganze Breite und fällt damit IMMER unter das
       Paar darüber — nicht erst, wenn es eng wird. Ein Raster wäre die
       zweite Beschreibung derselben zwei Zeilen; hier genügt die Basis. */
    flex-wrap: wrap;
    align-items: center;
    gap: var(--abstand-s);
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--surface-container);
    /* Über die volle Spaltenbreite, aus demselben Grund wie beim Fuß weiter
       oben: Was Chips und Suchfelder als Kante (--feld-kante) und der
       Reglergriff als Ring über ihren Kasten hinaus zeichnen, liegt im
       seitlichen Innenabstand der Spalte — dort deckt ein Kopf, der nur
       seinen Inhaltskasten abdeckt, nichts ab. */
    margin-inline: calc(-1 * var(--abstand-xl));
    padding: var(--abstand-xl) var(--abstand-xl) var(--abstand-m);
  }

  /* ⛔ DIE ÜBERSCHRIFT IST KEIN BEDIENELEMENT MEHR (23.09.2026). Sie trug
     bis dahin das Auf- und Zuklappen — ein Knopf, dem man ansah, dass er
     eine Überschrift ist, und nicht, dass er ein Knopf ist. Jetzt ist sie
     ein <p>: kein Zeiger, kein Fokusring, keine Zusage.

     ⛔ VERSALIEN IN SCHREIBMASCHINENSCHRIFT — dieselbe Bauart wie jeder
     Kicker der Seite (komponenten.css). Die Großschreibung entsteht über
     text-transform; im Markup steht gemischter Text, sonst buchstabieren
     Vorlesehilfen ihn teilweise. */
  .fs-titel {
    display: flex;
    align-items: center;
    gap: var(--abstand-xs);
    /* Nimmt den Platz, den der Schalter übriglässt — die Zahl rückt damit
       nicht an den rechten Rand, sondern bleibt am Wort, wie in der
       Vorlage. */
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    color: var(--on-surface);
    font-family: var(--schrift-mono);
    font-size: var(--label-medium);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    font-weight: 500;
  }

  /* ⛔ DIE BEIDEN HÖHEN SIND AUS DER BILDVORLAGE ABGEMESSEN (23.09.2026):
     32px für den Schalter, 38px für das Zurücksetzen. Der Abzug lag als
     2×-Bild vor — Maßstab über die Versalienhöhe der Überschrift bestimmt
     (18 zu 9 Bildpunkte), die Schriftgrößen sind in beiden dieselben. Der
     Kopf sitzt damit flacher über dem Formular, und der Reset bleibt das
     höhere und damit erste Element der beiden.

     ⚠️ DAS UNTERSCHREITET DIE 44px VON WCAG 2.5.5 (AAA) — bewusst und auf
     Ansage. Die AA-Schwelle (2.5.8, 24px) hält beides mit Abstand, und
     zwischen den Zielen liegt die Lücke des Kopfes; sie überlappen nicht.
     Eingeklappt, in der Werkzeugzeile, gilt weiterhin das Maß der Nachbarn
     (40px, siehe unten). */
  .fs-schalter {
    flex: 0 0 auto;
    min-height: 32px;
    gap: var(--abstand-xs);
    padding: 0 var(--abstand-m);
    color: var(--on-surface);
    font-size: var(--label-large);
  }

  /* ⛔ EIN UMRANDETER KNOPF ÜBER DIE VOLLE BREITE, kein Textlink mehr
     (23.09.2026). Vorher stand „Reset" als blasses Wort am Zeilenende —
     die einzige Stelle der Spalte, an der man ALLES zurücknehmen kann, und
     zugleich die leiseste. Die Kante in --primary ist hier die Aussage:
     Sie ist der einzige gefüllt wirkende Rahmen des Kopfes.

     ⚠️ `flex-basis: 100%` erzwingt die zweite Zeile — siehe .fs-kopf. */
  .fs-reset {
    flex: 1 0 100%;
    min-height: 38px;
    border-color: var(--primary);
  }
  .fs-reset[hidden] { display: none; }
  .fs-punkt[hidden] { display: none; }
  .fs-punkt { color: var(--on-surface-variant); }
  /* ⛔ NICHT MEHR `flex: 1 1 auto` (03.09.2026). Solange die Beschriftung den
     Platz füllte, schob sie die Zahl an das andere Ende der Zeile — dort las
     sie sich wie eine zweite Angabe statt wie ein Teil von „FILTER · 1".
     Zusammenhalten heißt hier: keiner der Teile wächst. */
  .fs-text { flex: 0 0 auto; text-align: left; }
  /* ⛔ ZWEI DARSTELLUNGEN DERSELBEN ZAHL. Ausgeklappt steht sie als Text
     hinter einem Trennpunkt („FILTER · 4", Bildvorlage); eingeklappt wird
     die runde Marke am Schalter sichtbar — dort ist sie das einzige Signal,
     dass gefiltert wird, und ein blasser Text neben einem Symbol wäre
     keines. Beide schreibt derselbe Aufruf (js/seite-liste.js). */
  .fs-zahl {
    flex: 0 0 auto;
    font-weight: 700;
  }
  .fs-zahl[hidden] { display: none; }
  /* Ausgeklappt trägt der Schalter Pfeil und Wort; Symbol und Marke gehören
     der Pille der Werkzeugzeile und warten hier. */
  .fs-pille,
  .fs-marke { display: none; }
  .fs-pfeil { display: inline-flex; transition: transform var(--dauer-kurz) ease; }
  .fs-pfeil svg { width: 18px; height: 18px; }
  /* Offen zeigt der Pfeil ZUR Leiste (einklappen, nach links), zu von ihr
     weg. Ein gedrehtes Dreieck statt zweier Symbole. */
  .fs-schalter[aria-expanded="true"] .fs-pfeil { transform: rotate(90deg); }
  .fs-schalter[aria-expanded="false"] .fs-pfeil { transform: rotate(-90deg); }

  /* ⛔ EINGEKLAPPT TRÄGT DIE SPALTE NICHTS MEHR (18.09.2026). Ihr Kopf steht
     in der Werkzeugzeile (siehe unten), das Formular behält als display:none
     seine DOM-Knoten samt ungespeicherter Zwischenstände — kein Neuaufbau
     beim Auf- und Zuklappen. Bis dahin blieb sie als 64px-Streifen stehen
     und trug Symbol und Zahl senkrecht gestapelt. */
  .filter-sidebar--zu { display: none; }

  /* ---------- Eingeklappt: die Pille in der Werkzeugzeile (18.09.2026) ----

     DERSELBE KNOTEN wie der Kopf der offenen Spalte — verschoben, nicht neu
     gebaut (js/seite-liste.js). Er nimmt hier die Form der übrigen Werkzeuge
     an: umrandete Pille, 40px hoch, wie Sortierung und Bauartwahl daneben.

     ⚠️ Typografie und Farbe bleiben die des Schalters selbst (Versalien in
     Schreibmaschinenschrift, --on-surface): Auf- und zugeklappt steht damit
     dieselbe Beschriftung in derselben Schrift, und es gibt keine zweite
     Fassung, die beim nächsten Umbau nachzuziehen wäre. */
  /* ⛔ DIE PILLE IST DER SCHALTER SELBST (23.09.2026), nicht mehr die Hülle.
     Seit die Überschrift kein Bedienelement mehr ist, wäre eine umrandete
     Hülle aus Text UND Knopf eine Fläche, die zur Hälfte klickbar ist —
     genau die Unklarheit, die in der Spalte gerade beseitigt wurde. Die
     Hülle gibt hier also nur noch den Platz ab. */
  .listen-kopf > .fs-kopf {
    display: flex;
    /* Sie gibt so wenig nach wie die Knöpfe daneben — in einer Zeile, die
       umbricht, wäre ein schrumpfender Filter das erste, was zerläuft. */
    flex: 0 0 auto;
    /* ⛔ RÜCKNAHME DER SPALTENFASSUNG. Dort klebt der Kopf, deckt den
       durchlaufenden Inhalt ab und greift dafür in den seitlichen
       Innenabstand der Spalte hinaus. In der Werkzeugzeile klebt die Zeile
       selbst (.listen-kopf), und der Überstand zöge die Pille um 32px nach
       links aus ihr heraus. */
    position: static;
    flex-wrap: nowrap;
    margin-inline: 0;
    padding: 0;
    background: none;
  }
  /* ⛔ 40px WIE DIE NACHBARN. Hier bestimmt die Zeile das Maß: Sortierung,
     Versandschalter und Bauartwahl stehen daneben, und eine um 4px höhere
     Pille dazwischen läse sich als Fehler. In der Spalte steht der Schalter
     allein und trägt dort seine 44px. */
  .listen-kopf > .fs-kopf .fs-schalter {
    flex: 0 0 auto;
    min-height: 40px;
    gap: var(--abstand-s);
    padding: 0 var(--abstand-l);
  }
  /* ⛔ VIER TEILE ENTFALLEN, JEDER AUS EIGENEM GRUND. Die Überschrift wäre
     neben einer Pille, die dasselbe Wort trägt, dasselbe zweimal. Der
     Trennpunkt gehört zu ihrer Schreibweise „FILTER · 4". Das Zurücksetzen
     entfällt, weil das, was zurückzunehmen wäre, eingeklappt gar nicht zu
     sehen ist. Und der Pfeil wäre neben einer Pille, die sich schon als
     Knopf liest, ein zweites Vorzeichen für dieselbe Geste. */
  .listen-kopf > .fs-kopf .fs-titel,
  .listen-kopf > .fs-kopf .fs-reset,
  .listen-kopf > .fs-kopf .fs-schalter-text,
  .listen-kopf > .fs-kopf .fs-pfeil { display: none; }
  /* Das Wort „Filter" steht eingeklappt IM Knopf, samt Symbol — nicht als
     Überschrift daneben. */
  .listen-kopf > .fs-kopf .fs-pille {
    display: inline-flex;
    align-items: center;
    gap: var(--abstand-s);
  }
  /* ⛔ DIE ZAHL IST HIER DIE RUNDE MARKE. Eingeklappt ist sie das einzige
     Signal, dass überhaupt gefiltert wird — als blasser Text neben einem
     Symbol wäre sie keines.

     ⚠️ `position: static` ist Pflicht: .zaehlmarke liegt von Haus aus
     absolut (layout.css), und der Knopf ist mit `position: relative` ihr
     Bezug — sie läge sonst in seiner linken oberen Ecke. */
  .listen-kopf > .fs-kopf .fs-marke {
    display: inline-block;
    position: static;
    min-width: 18px;
    padding: 0 5px;
    font-size: var(--label-small);
  }

  /* ---------- Breite Spalte: Kriterien nebeneinander ----------
     Das Formular ist ein Raster mit zwei Bahnen. Volle Breite ist die Regel
     (Chips, Farbe, Regler brauchen die Zeile); kompakte Abschnitte — die
     Schalter — stehen zu zweit nebeneinander statt endlos untereinander. */
  .filter-sidebar [data-sidebar-formular] {
    display: grid;
    /* minmax(0, 1fr) statt 1fr — dieselbe Falle wie beim Listenraster: Eine
       1fr-Spur hat als Mindestmaß `auto`, also die Inhaltsbreite. Ein langer
       Schaltertext oder ein breiter Chip könnte die Spur damit über die
       Spalte hinaus aufdrücken, statt umzubrechen. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: var(--abstand-l);
  }
  .filter-sidebar [data-sidebar-formular] > * { grid-column: 1 / -1; }
  .filter-sidebar [data-sidebar-formular] > .sheet-abschnitt--kompakt { grid-column: auto; }

  /* ⛔ SCHMALE SPALTE: die Schalter untereinander (20.08.2026).
     Das ist die Bedingung, unter der PD.zustand.FILTER_BREITE_MIN von 280px
     überhaupt stimmt. Gemessen: Die breiteste Schalterzeile („Abgekündigte
     ausblenden" auf /drucker) braucht mindestens 173px; zu zweit
     nebeneinander sind das mit der 16px-Lücke 362px Inhaltsbreite, also gut
     425px Spalte. Zu zweit ist die Spalte damit nach unten praktisch
     unbeweglich. Einspaltig bleibt der Fuß „Zurücksetzen / Anwenden" mit
     217px der breiteste Baustein — daher die 280px.

     Die Schwelle liegt bei 337px INHALTSbreite (= 400px Spalte abzüglich
     48px Innenabstand und 15px Rollbalkenreserve) und damit bewusst unter
     den 420px der Vorgabe: Wer nie zieht, sieht dieselbe Aufteilung wie
     bisher. ⚠️ Zwischen 400 und 425px bleiben die Schalter zu zweit und
     stehen ein paar Pixel enger, als ihr Inhalt gern hätte — genau der
     Zustand, in dem /drucker schon vor dieser Änderung war. */
  @container (max-width: 337px) {
    .filter-sidebar [data-sidebar-formular] { grid-template-columns: minmax(0, 1fr); }
    .filter-sidebar [data-sidebar-formular] > .sheet-abschnitt--kompakt { grid-column: 1 / -1; }
  }

  /* ==================================================================
     DIE VIER GRUPPEN DER SPALTE (Bildvorlage, 03.09.2026)
     ==================================================================

     Material · Rollengewicht · Shop · Weitere, darunter der Weg zu allem
     Übrigen. Jede Gruppe trägt eine Versalienzeile und darunter eine Liste
     aus Kontrollkästchen mit Trefferzahl.

     ⛔ Sie füllen die GANZE Spaltenbreite. Das zweispaltige Raster darüber
     galt den kompakten Schalterabschnitten des alten Formulars; eine Liste
     aus Kästchen zu zweit nebeneinander wäre in einer 290px-Spalte eine
     Zeile aus abgeschnittenen Wörtern. */
  .filter-sidebar [data-sidebar-formular] > .fg,
  .filter-sidebar [data-sidebar-formular] > .fg-erweitert { grid-column: 1 / -1; }

  .fg { padding-block: var(--abstand-m) 0; }
  /* Eine Haarlinie zwischen den Gruppen — dieselbe Ausnahme wie im
     Klappmenü: In einer Fläche ohne eigenen Grund kann Abstand allein die
     Gruppen nicht trennen, wenn ihre Zeilen selbst schon Abstand tragen. */
  .fg + .fg { border-top: 1px solid var(--outline-variant); }

  .fg-titel {
    margin: 0 0 var(--abstand-s);
    font-family: var(--schrift-mono);
    font-size: var(--label-medium);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    font-weight: 500;
    color: var(--on-surface-variant);
  }

  /* ⚠️ NUR die Druckerspalte führt ein Suchfeld INNERHALB einer Gruppe:
     Hersteller (17 Werte) und „Kann drucken" (32) sind zu lang für eine
     gekürzte Liste allein, und die Freitextsuche bleibt, wo sie seit jeher
     steht. Den Abstand bringt sonst `.sheet-abschnitt > * + *` mit — den es
     in einer .fg-Gruppe nicht gibt. */
  .fg > .filter-suchfeld {
    width: 100%;
    margin-bottom: var(--abstand-s);
  }

  .fg-liste { display: flex; flex-direction: column; }

  /* Die ganze Zeile ist das Ziel, nicht nur das Kästchen: 36px hoch, über die
     volle Breite — das ist deutlich mehr als die 24px aus WCAG 2.5.8 und
     erspart das Zielen auf ein 16px-Quadrat. */
  .fg-zeile {
    display: flex;
    align-items: center;
    gap: var(--abstand-s);
    min-height: 36px;
    padding: var(--abstand-xxs) var(--abstand-xs);
    margin-inline: calc(-1 * var(--abstand-xs));
    border-radius: var(--radius-s);
    cursor: pointer;
    font-size: var(--body-medium);
    color: var(--on-surface);
  }
  .fg-zeile:hover { background: var(--zustand-hover); }
  .fg-zeile:has(.fg-kasten:focus-visible) { outline: 2px solid var(--primary); }

  /* ⚠️ accent-color statt eines nachgebauten Kästchens: Das native Element
     bringt Tastatur, Vorlesehilfen und den Hochkontrastmodus des Systems
     mit — alles Dinge, die ein <span> mit Häkchen erst nachbilden müsste. */
  .fg-kasten {
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    margin: 0;
    accent-color: var(--primary);
    cursor: pointer;
  }

  .fg-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* ⚠️ Schreibmaschinenschrift mit gleicher Ziffernbreite: Die Zahlen stehen
     untereinander und sollen eine Flucht bilden — derselbe Grund wie in der
     Tabellenansicht. */
  .fg-zahl {
    flex: 0 0 auto;
    font-family: var(--schrift-mono);
    font-size: var(--label-medium);
    font-variant-numeric: tabular-nums;
    color: var(--on-surface-variant);
  }

  /* ⛔ DIE DRUCKERSPALTE TRÄGT DIESELBEN GRUPPENTITEL (03.09.2026). Ihre
     Kriterien sind andere und bleiben es — angeglichen ist allein, wie ihre
     Überschriften aussehen. Ohne diese Regel stünden neben der Filterspalte
     der Produktlisten Serifenüberschriften in einer Spalte, die sonst
     Zeichen für Zeichen dasselbe Gerüst benutzt. */
  .dr-filter .krit-kopf h3 {
    margin: 0;
    font-family: var(--schrift-mono);
    font-size: var(--label-medium);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    font-weight: 500;
    color: var(--on-surface-variant);
  }

  /* Der Regler bringt seine eigene Überschrift mit (kritKopfHtml), deshalb
     ohne .fg-titel — und deshalb hier der Ausgleich für den Abstand, den die
     Überschrift sonst trüge. */
  .fg--regler { padding-block: var(--abstand-m) 0; }
  .fg--regler .sheet-abschnitt { padding: 0; }
  /* ⛔ DIESELBE VERSALIENZEILE WIE .fg-titel. Der Regler bringt seinen Kopf
     über kritKopfHtml mit (samt der (i)-Erklärung, die er behalten soll) —
     ohne diese Angleichung stünde zwischen „MATERIAL" und „SHOP" eine
     Überschrift in anderer Schrift, Größe und Farbe, und die Spalte läse sich
     als zwei verschiedene Formulare. */
  .fg--regler .krit-kopf h3 {
    margin: 0;
    font-family: var(--schrift-mono);
    font-size: var(--label-medium);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    font-weight: 500;
    color: var(--on-surface-variant);
  }
  .fg--regler .krit-kopf { margin-bottom: var(--abstand-s); }

  /* „Mehr anzeigen" / „Weniger anzeigen" unter einer gekürzten Gruppe.
     Ein Knopf, kein <a>: Er führt nirgendwohin, er klappt auf. Sichtbar ist
     er trotzdem ein Verweis — deshalb Akzentfarbe und Unterstreichung. */
  .fg-mehr {
    appearance: none;
    -webkit-appearance: none;
    border: 0;
    background: none;
    display: block;
    width: 100%;
    text-align: left;
    min-height: 32px;
    padding: var(--abstand-xxs) var(--abstand-xs);
    margin-inline: calc(-1 * var(--abstand-xs));
    border-radius: var(--radius-s);
    font-family: inherit;
    font-size: var(--body-small);
    color: var(--primary);
    text-decoration: underline;
    cursor: pointer;
  }
  .fg-mehr:hover { background: var(--zustand-hover); }
  /* ⚠️ Gedämpft, aber NICHT unbedienbar: Ein gesetzter Filter hinter der
     Grenze hält die Gruppe offen, und der Knopf nennt beim Klick den Grund.
     Die Unterstreichung fällt weg — sie verspricht eine Wirkung, die es in
     diesem Zustand nicht gibt. */
  .fg-mehr[aria-disabled="true"] {
    color: var(--on-surface-variant);
    text-decoration: none;
    cursor: default;
  }

  /* Die Wertzeile des Bereichsreglers trägt in der Spalte die Akzentfarbe:
     Sie ist die einzige Stelle, an der die eingestellte Menge als Text steht,
     und damit das Gegenstück zur Trefferzahl der übrigen Gruppen. */
  .filter-sidebar .bereich-werte { color: var(--primary); }

  .fg-erweitert {
    width: 100%;
    margin-block: var(--abstand-l) var(--abstand-xl);
  }

  /* ⚠️ NACH der Rasterregel oben, und das ist tragend: Beide Selektoren
     wiegen gleich (eine Klasse, ein Attribut), also entscheidet allein die
     Reihenfolge. Stand diese Zeile davor, gewann das display:grid — und das
     Formular der eingeklappten Spalte lief sichtbar über die Ergebnisliste.
     display:none statt Ausblendetricks: Die DOM-Knoten samt ungespeicherter
     Zwischenstände bleiben erhalten, nur gezeichnet wird nichts. */
}

/* ---------- Kriteriumskopf mit (i)-Erklärung ----------
   Gilt in Seitenleiste UND Sheet — die Tooltips ersetzen die früheren
   Beschreibungssätze unter den Kriterien (Texte: docs/filter-tooltips.md,
   Tabelle TOOLTIPS in js/seite-liste.js). */

.krit-kopf {
  display: flex;
  align-items: center;
  gap: var(--abstand-xs);
  /* ⚠️ Umbrechen ist tragend, nicht Kosmetik: Die aufklappbare Erklärung liegt
     INNERHALB dieser Zeile und nimmt mit `flex-basis: 100%` eine eigene Zeile
     darunter ein. Ohne den Umbruch stünde sie neben dem (i) und drückte die
     Kopfzeile auseinander. */
  flex-wrap: wrap;
}
.krit-kopf h3 { margin: 0; }
.krit-kopf--schalter .schalter-zeile { flex: 1 1 auto; min-width: 0; }

/* ---------- Aufklappbare Erklärung eines Kriteriums (10.08.2026) ----------
   Sie ersetzt in der Filterspalte und im Sheet die frühere schwebende Blase.
   Der Grund steht ausführlich in js/seite-liste.js bei infoHtml; kurz: Die
   Blase öffnete 4px unter dem (i), das erste Bedienelement beginnt 12px
   darunter — sie MUSSTE es verdecken, und ausweichen konnte sie in einer
   420px-Spalte nirgends.

   Als eigene Zeile IM FLUSS schiebt sie den Inhalt darunter nach unten und
   überlagert nichts. Kein Aufklappen bei :hover: Das erzeugte bei jeder
   Mausbewegung über die Spalte einen Layoutsprung. */
.krit-erklaerung {
  flex: 1 0 100%;
  margin: var(--abstand-xs) 0 0;
  padding: var(--abstand-s) var(--abstand-m);
  border-radius: var(--radius-s);
  /* Abgesetzte Fläche statt Rahmen: Sie liest sich als Beiwerk zum Kriterium
     und nicht als weiteres Bedienelement. */
  background: var(--surface-container-high);
  color: var(--on-surface-variant);
  font-size: var(--label-medium);
  font-weight: 400;
  line-height: var(--zeile-normal);
}


.krit-info {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  flex: 0 0 auto;
  border: 0;
  border-radius: var(--radius-pille);
  background: none;
  cursor: help;
  color: var(--on-surface-variant);
}
.krit-info svg { width: 16px; height: 16px; }
.krit-info:hover { color: var(--on-surface); }

/* Das offene (i) bleibt sichtbar als das, was gerade erklärt wird.
   ⚠️ NACH der :hover-Regel: Beide wiegen gleich (Klasse plus Attribut bzw.
   Pseudoklasse), also entscheidet die Reihenfolge — davor gewann beim
   Überfahren eines offenen (i) wieder die gewöhnliche Farbe. */
.krit-info[aria-expanded="true"],
.krit-info[aria-expanded="true"]:hover {
  color: var(--primary);
  background: var(--surface-container-high);
}

/* Die schwebende Erklärblase — seit dem 10.08.2026 NUR NOCH für `.krit-info--blase`,
   und die gibt es genau einmal: an der Bauartzeile auf /drucker. Sie steht in
   der klebenden Werkzeugzeile, wo eine aufklappende Erklärung die ganze
   Geräteliste verschöbe, und hat unter sich kein Bedienelement, das sie
   verdecken könnte.

   ⛔ Die Einschränkung auf die Modifikatorklasse ist Pflicht, nicht Kosmetik:
   `content: attr(data-tooltip)` liefert ohne das Attribut einen LEERSTRING —
   das Pseudoelement bliebe mit Polsterung und Hintergrund bestehen und
   erschiene beim Überfahren als leerer dunkler Kasten.

   Sichtbar beim Überfahren und — weil das (i) ein echter Knopf ist — beim
   Antippen (Fokus). UNTER dem Symbol, nicht darüber: Die Zeile ist breit, nach
   unten ist Platz. */
.krit-info--blase::after {
  content: attr(data-tooltip);
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 6;
  width: max-content;
  max-width: 260px;
  padding: var(--abstand-s) var(--abstand-m);
  border-radius: var(--radius-s);
  background: var(--inverse-surface);
  color: var(--inverse-on-surface);
  font-size: var(--label-medium);
  font-weight: 400;
  line-height: var(--zeile-normal);
  text-align: left;
  white-space: normal;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dauer-kurz) ease;
  pointer-events: none;
}
.krit-info--blase:hover::after,
.krit-info--blase:focus-visible::after,
.krit-info--blase:focus::after {
  opacity: 1;
  visibility: visible;
}

/* ---------- Regler mit Wertblase am Griff ----------
   Für alle Schieber des Filters (Gewicht, Gebinde, Temperaturen, Toleranz).
   Die Blase folgt dem Griff schon beim Ziehen (js: verbindeReglerWerte);
   der Platz über der Spur ist fest reserviert, damit beim Erscheinen des
   ersten Werts nichts springt. */

.regler {
  position: relative;
  padding-top: 30px;
}
.regler input[type="range"] {
  width: 100%;
  display: block;
  /* ⚠️ Chrome gibt `input[type=range]` von sich aus `margin: 2px`. Zusammen
     mit width:100% ragte die Spur dadurch 2px über die rechte Kante der
     Filterspalte hinaus — gemessen, nicht vermutet. Der Rücksetzer gehört
     ausdrücklich hierher und nicht in basis.css: Er gilt dem Regler, nicht
     allen Formularelementen. */
  margin: 0;
}

.regler-wert {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  padding: 2px var(--abstand-s);
  border-radius: var(--radius-pille);
  background: var(--surface-container-highest);
  color: var(--on-surface);
  font-size: var(--label-medium);
  font-weight: 500;
  white-space: nowrap;
  pointer-events: none;
}

/* Die Endbeschriftungen unter der Spur — dieselbe Bauform wie die
   eng/weit-Skala der Farbtoleranz. */
.regler-skala {
  display: flex;
  justify-content: space-between;
  margin-top: var(--abstand-xxs);
  font-size: var(--label-medium);
  color: var(--on-surface-variant);
}

/* Einblendanimation der Kacheln nach Filterwechsel. */
.kachel-liste--frisch .filament-kachel { animation: seite-einblenden 250ms ease-out; }

/* ---------- Detailseite eines Angebots (Preisvergleich-Layout) ---------- */

/* ⛔ OHNE HÖCHSTBREITE (seit 24.09.2026, Bildvorlage): Die Detailseite geht
   über die ganze Breite von <main> (höchstens 1600px, css/layout.css) — die
   Angebote stehen als Karten nebeneinander und brauchen die Breite. Bis dahin
   stand sie wie Material- und Textseiten auf 1120px. */
.detail { min-width: 0; }
.detail-hinweis { padding: var(--abstand-l) 0; color: var(--on-surface-variant); }

/* ⛔ `.detail-zurueckzeile` IST AM 12.09.2026 ENTFALLEN. Sie trug die drei
   improvisierten Zurück-Wege des Projekts (Ladezustand der Detailseite,
   Materialartikel, Merkzettel-Ordner); an ihre Stelle tritt die gemeinsame
   `.zurueckzeile` in css/komponenten.css. Wer die alte Klasse wieder einsetzt,
   baut einen zweiten Zurück-Weg neben dem einen — und der ruft dann wieder
   history.back() ohne zu prüfen, ob es ein Zurück gibt. */

/* Karten tragen ihre eigene Fläche und keine Umrandung mehr. Die Linie war
   nötig, solange die Fläche (--surface-container-lowest) nur 1,05:1 neben der
   Seite lag; mit --surface-container ist die Karte auch ohne sie als Einheit
   zu erkennen. */
.detail-karte {
  border-radius: var(--radius-m);
  background: var(--surface-container);
  padding: var(--abstand-xl);
}
/* Überschriftenstufe der Material-Detailseite (.material-karte h2), damit
   beide Seiten wie aus einem Guss wirken: 26px statt der vorherigen 20px.
   ⚠️ `.detail-weitere h2` ohne `>`: Die Überschrift der Angebote steht in
   einer Kopfzeile (.dw-kopf) neben dem Satz zur Reihenfolge; die Shopnamen
   der Karten sind <h3> und bleiben davon unberührt. */
.detail-temp > h2,
.detail-harz > h2,
.detail-anforderungen > h2,
.detail-datenkarte > h2,
.detail-verlauf > h2,
.detail-weitere h2 {
  font-size: var(--headline-small);
  font-weight: 500;
  line-height: var(--zeile-eng);
  margin-bottom: var(--abstand-l);
}

/* ---------- Einklappbare Abschnitte der Detailseite (02.09.2026) ----------

   Betroffen sind der Preisverlauf (seit 24.09.2026) und die beschreibenden
   Karten (Produktdaten, Druckprofil, „Belichtung und Gerät", Anforderungen);
   Vorgabe auf jeder Breite: zu. ⛔ Kaufbox und Angebotsliste klappen NIE —
   ein Preisvergleich, dessen Preise man erst aufklappen muss, ist keiner.
   Welche Karte klappbar ist, entscheidet allein js/seite-detail.js über die
   Klasse .detail-klapp.

   ⛔ DIE HÖHE FÄHRT ÜBER grid-template-rows VON 1fr AUF 0fr, nicht über
   max-height und nicht über eine in JavaScript gemessene Pixelhöhe.

   max-height braucht einen geratenen Höchstwert: Zu klein schneidet er
   Inhalte ab — und „Inhalte und Werte bleiben unverändert" wäre gebrochen,
   ohne dass irgendwo ein Fehler erschiene. Zu groß macht die Übergangszeit zu
   einer Lüge, weil der sichtbare Teil der Bewegung schon vorbei ist. Eine
   gemessene Pixelhöhe wiederum ist beim nächsten Umbruch falsch: Wer bei
   offener Karte das Fenster schmaler zieht, hätte eine festgeschriebene Höhe
   für einen Inhalt, der inzwischen zwei Zeilen mehr braucht.

   Die Rasterzeile misst sich dagegen selbst — in jeder Lage, bei jeder
   Fensterbreite, ohne eine einzige Zahl in JavaScript.

   ⚠️ Der Preis: Die Animation der Rasterzeile braucht Chrome 117, Safari 17.2
   oder Firefox 120 — eine Browsergeneration jünger als die Containerabfragen,
   die das Projekt schon voraussetzt. Wo sie fehlt, klappt der Abschnitt
   trotzdem auf und zu, nur ohne Gleitweg: `0fr` ist eine gewöhnliche
   Rasterangabe und wird überall verstanden, animiert wird sie nur nicht. Der
   Rückfall ist damit „kein Übergang", nie „geht nicht". */

.klapp-schalter {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--abstand-m);
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  /* Die Überschrift trägt Größe und Gewicht; der Schalter erbt beides
     (basis.css: `button, input { font: inherit }`). Ohne text-align stünde
     der Titel in der Mitte — ein Knopf zentriert seine Beschriftung. */
  text-align: left;
  cursor: pointer;
}

.klapp-pfeil {
  display: inline-flex;
  flex: 0 0 auto;
  padding: var(--abstand-xs);
  border-radius: var(--radius-pille);
  color: var(--on-surface-variant);
  transition: transform var(--dauer-mittel) ease,
              background-color var(--dauer-kurz) ease;
}
.klapp-pfeil svg { width: 24px; height: 24px; }

/* Die Rückmeldung sitzt auf dem Chevron, nicht auf der ganzen Zeile: Der
   Schalter ist so breit wie die Karte, und eine Fläche über die volle Breite
   sähe aus, als sei die Überschrift ausgewählt. Zustandsebene statt
   Flächenstufe — die Karte liegt selbst schon auf --surface-container. */
.klapp-schalter:hover .klapp-pfeil { background: var(--zustand-hover); }
.klapp-schalter:active .klapp-pfeil { background: var(--zustand-aktiv); }

/* Eingeklappt zeigt der Pfeil nach rechts — dieselbe Drehung und dieselbe
   Richtung wie am Kopf der Filterspalte (.fs-pfeil), damit dieselbe Geste
   nicht zweimal verschieden aussieht. */
.detail-klapp[data-offen="false"] .klapp-pfeil { transform: rotate(-90deg); }

/* ⚠️ Die Überschrift gibt ihren Abstand nach unten ab — er wandert in den
   Inhalt. Bliebe er an der Überschrift, stünden unter dem zugeklappten Titel
   16px Leere, die niemand erklären kann: Die Rasterzeile darunter ist dann 0
   hoch, ihr eigener Abstand aber nicht. Im Inhalt wird er mit eingeklappt. */
.detail-klapp > h2 { margin-bottom: 0; }

.klapp-huelle {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows var(--dauer-mittel) ease;
}
.detail-klapp[data-offen="false"] .klapp-huelle { grid-template-rows: 0fr; }

/* ⛔ DREI EBENEN, UND DIE DRITTE IST NICHT ZU SPAREN.

   Der erste Versuch hatte den Innenabstand am zugeschnittenen Element selbst.
   Gemessen blieben eingeklappt **16px** stehen: Bei `box-sizing: border-box`
   (basis.css, für alles) kann eine Höhe von 0 den Innenabstand nicht
   auffressen — er ist die Untergrenze des Kastens. Unter jeder zugeklappten
   Überschrift stand also ein Streifen Leere, den niemand erklären kann.
   Dasselbe gilt für einen Außenabstand: Der zählt zur Höhe des Rasters.

   Deshalb trägt das Fenster den Zuschnitt und NUR ihn, und der Abstand sitzt
   eine Ebene tiefer, wo er mit eingeklappt wird. */
.klapp-fenster {
  /* Beides ist Pflicht: Ohne min-height: 0 wird ein Rasterkind nie kleiner
     als sein Inhalt, die Zeile führe auf 0fr und das Kind bliebe stehen. Und
     ohne den Zuschnitt stünde der volle Inhalt über der Karte hinaus. */
  min-height: 0;
  overflow: hidden;
}

.klapp-inhalt {
  /* Der Abstand, den die Überschrift oben abgegeben hat. */
  padding-top: var(--abstand-l);
}

/* ⛔ ZUGEKLAPPT IST DER INHALT AUCH FÜR TASTATUR UND VORLESEHILFE WEG.

   Eine Zeile der Höhe 0 mit Zuschnitt ist nur OPTISCH weg: Der Text bleibt im
   Baum, die Tabulatortaste läuft hindurch, und eine Vorlesehilfe liest einen
   Abschnitt vor, den der Benutzer ausdrücklich zugeklappt hat. `visibility`
   nimmt beides — und ist im Gegensatz zu `display: none` animierbar, weil es
   das Layout nicht anfasst.

   ⚠️ Die Verzögerung gehört zum Schließen und nur dorthin: Beim Zuklappen
   bleibt der Inhalt sichtbar, bis die Zeile unten ist; beim Aufklappen ist er
   sofort da. Ohne sie verschwände der Text schlagartig und die Zeile führe
   danach ins Leere.

   ⚠️ Das ist NICHT der Fall der rechten Hauptleiste, wo `visibility: hidden`
   ausdrücklich verboten ist: Dort wäre der einzige Weg zum Aufklappen selbst
   unfokussierbar geworden, ein Weg, der sich selbst verschließt. Hier bleibt
   der Schalter immer erreichbar — er steht in der Überschrift, außerhalb des
   zugeklappten Bereichs. Ein Prüffall, der pauschal Fokussierbarkeit
   verlangt, wäre an dieser Stelle also falsch. */
.detail-klapp[data-offen="false"] .klapp-inhalt {
  visibility: hidden;
  transition: visibility 0s linear var(--dauer-mittel);
}

/* Layout (24.09.2026, Bildvorlage): EINE Spalte über die volle Breite, in
   DOM-Reihenfolge — Kopf, Angebote, Preisverlauf, Produktdaten, Druckprofil
   bzw. Belichtung, Anforderungen, Vertrauensblock, Preisfußzeile. Ab 1024px
   stehen allein Produktdaten und die kategorieeigene Karte nebeneinander.

   ⛔ BIS ZUM 24.09.2026 GAB ES HIER EINE HAFTENDE SEITENSPALTE (Kaufbox und
   Vertrauensblock, 320px) und dazu zwei Fassungen von grid-template-areas —
   mit und ohne Anforderungszeile. Beides ist entfallen: Der Preis steht im
   Kopf, jedes Angebot als Karte.

   ⛔ JEDES KIND STEHT VON SICH AUS ÜBER DIE VOLLE BREITE (`grid-column:
   1 / -1`), nur die beiden benannten Karten teilen sich eine Zeile. Mit
   benannten Flächen landete ein Kind ohne Platz in der nächsten freien Zelle
   — eine Karte stand dadurch einmal unsichtbar hinter der Seitenspalte. Mit
   dieser Vorgabe landet eine neue Karte schlimmstenfalls in einer eigenen,
   vollen Zeile, und eine fehlende Karte hinterlässt keine leere Zeile.

   Die Verlaufskarte steht IMMER im Raster, auch wenn noch keine Zahlen da
   sind: Sie trägt in jedem Fall einen Inhalt (Ladezustand, Diagramm oder
   Hinweis). Ein Bereich, der mal da ist und mal nicht, hinterließe beim
   Nachladen eine Lücke — und die Seite spränge, sobald die Antwort eintrifft. */
.detail-layout {
  display: grid;
  /* ⛔ minmax(0, 1fr), nicht 1fr: Eine Rasterspur wird von sich aus nie
     schmaler als ihr Inhalt. Die frühere Angebotstabelle drückte die einzige
     Spur bei 320px auf 302px auf — die Seite wurde 13px breiter als das
     Fenster, und zwar mit ALLEN Karten. Dieselbe Falle wie beim Kopfbereich
     der Startseite. */
  grid-template-columns: minmax(0, 1fr);
  /* Abstand zwischen Abschnitten wie auf der Material-Detailseite
     (.material-karte margin-bottom). */
  gap: var(--abstand-2xl);
  align-items: start;
}
/* ⛔ min-width: 0 gehört dazu: Ein Rasterkind bekommt von sich aus
   `min-width: auto`, also seinen Inhalt als Untergrenze. */
.detail-layout > * {
  grid-column: 1 / -1;
  min-width: 0;
}

/* ⛔ KEIN `order` (seit 24.09.2026): Das Markup steht in der Reihenfolge, in
   der die Seite gelesen wird, auf jeder Breite. */

@media (min-width: 1024px) {
  .detail-layout { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  /* Die kategorieeigene Karte: Filament zeigt hier sein Druckprofil, Resin
     seine Belichtung. Nie beide zugleich — es gibt kein Produkt, das beides
     hätte. ⚠️ Die Regel steht NACH `.detail-layout > *` und wiegt gleich;
     allein die Reihenfolge entscheidet. */
  .detail-datenkarte,
  .detail-temp,
  .detail-harz { grid-column: auto; }
}

/* --- Der Kopf: Bild · Text · Preis (24.09.2026, Bildvorlage) ---

   Unter 768px zwei Zeilen: Bild und Text oben, Preis und Knöpfe darunter.
   Ab 768px eine Zeile — das Bild in fester Spur, der Text nimmt, was übrig
   bleibt, der Preisblock steht rechts in seiner eigenen Breite. */
.detail-kopf {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas:
    "bild text"
    "kauf kauf";
  gap: var(--abstand-l) var(--abstand-xl);
  align-items: center;
}
/* Das Produktbild — eine Stufe abgesetzt von der Kartenfläche, sonst
   schwebte der Platzhalter im Nichts. Vollständig eingepasst statt
   zugeschnitten: Ein abgeschnittener Spulenrand wäre genau das, was man
   prüfen will. */
.detail-kopf-bild {
  grid-area: bild;
  align-self: start;
  width: 72px;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-m);
  overflow: hidden;
  background: var(--surface-container-high);
}
.detail-kopf-bild img { width: 100%; height: 100%; object-fit: contain; }
.detail-kopf-text { grid-area: text; min-width: 0; }
.detail-kaufbox {
  grid-area: kauf;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--abstand-l) var(--abstand-xl);
}

@media (min-width: 768px) {
  .detail-kopf {
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "bild text kauf";
  }
  .detail-kopf-bild { width: 104px; align-self: center; }
  .detail-kaufbox { flex-wrap: nowrap; }
  .kaufbox-preisblock { text-align: right; }
}

/* Die Begründung der Kompatibilität steht im Kopf ohne eigene Fläche: Dort
   ist sie eine Zeile unter dem Namen (Bildvorlage), keine Karte in der
   Karte. Der Inhalt bleibt vollständig — Stufe, Gerät, Gründe. */
.detail-kopf .kompatibilitaets-block {
  margin-top: var(--abstand-s);
  padding: 0;
  background: none;
}
.detail-kopf .kompatibilitaets-gruende { font-size: var(--body-small); }

/* ==================================================================
   DER ERWEITERTE FILTER ALS POP-UP (Bildvorlage 2, 03.09.2026)
   ==================================================================

   Kopf mit Zählmarke und Umschalter, Chipreihe der gesetzten Kriterien, drei
   Spalten, Fußleiste. Er ersetzt das frühere Filter-Sheet auf JEDER Breite —
   die Vorlage ist responsiv gemeint.

   ⛔ Die Breite steht am `.dialog`, der ihn trägt: Die Grundregel dort deckelt
   auf 400px (Bestätigungsdialoge), und drei Spalten passen darin nicht. Die
   Ausnahme hängt an `:has(.ef)` und gilt damit nur für dieses eine Fenster —
   jeder andere Dialog behält seine 400px. */
.dialog:has(.ef) {
  /* ⛔ KLEINER SEIT DEM 17.09.2026: 1180 × 900 → 1120 × 780. Es füllte auf
     einem 1440er Schirm 82 % der Breite und sah damit aus wie eine eigene
     Seite statt wie ein Fenster über der Liste.

     ⛔ 1120px IST DAS PROJEKTWEITE INHALTSMASS — dieselbe Grenze wie
     `.detail-layout`, `.material-seite` und `.textseite`. Ein Fenster, das
     dieselbe Kante hat wie die Seiten darunter, liest sich als Teil
     derselben Seite statt als Fremdkörper darüber. Eine eigene Zahl hätte
     dieselbe Breite ungefähr getroffen und wäre beim nächsten Umbau des
     Seitenmaßes stillschweigend daneben gelaufen.

     ⛔ GEÄNDERT SIND NUR DIE PIXELGRENZEN, NICHT DIE FENSTERANTEILE (96vw,
     92vh bleiben). Die erste Fassung senkte beides und war gemessen ein
     Rückschritt: Auf einem 1100px breiten Schirm bindet der Anteil, nicht die
     Pixelzahl — mit 90vw blieben dort 682px Inhalt statt 740, und der
     Farbkreis-Reiter stapelte, der vorher zweispaltig stand. Zu groß war das
     Fenster nie auf schmalen Schirmen, sondern auf breiten.

     ⛔ WEITER GEHT ES NICHT, OHNE ETWAS ANDERES KAPUTTZUMACHEN: Der
     Farbkreis-Reiter stapelt unter 720px Inhaltsbreite (Containerabfrage
     `ef-inhalt`), und die Inhaltsbreite ist `Breite − 2×Polster −
     Reiterspalte − Lücke − Polster` = Breite − 356 (Spalte 260 seit dem
     Abend des 17.09.2026). Bei 1120 sind das **764px** — gestapelt würde der
     Reiter HÖHER, und weil alle Reiter übereinanderliegen, wüchse damit das
     ganze Fenster. Ein schmaleres Fenster wäre also ein höheres. */
  max-width: min(96vw, 1120px);
  width: 100%;
  /* Die Spalten können lang werden (Farbkreis, vier erweiterte Kriterien).
     Gerollt wird IM Dialog, nicht die Seite dahinter. */
  max-height: min(92vh, 780px);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: var(--abstand-xl);
}

/* ⛔ DER DIALOG ROLLT SEIT DEM 09.09.2026 NICHT MEHR ALS GANZES. Kopfzeile
   samt Chipreihe, Reiterspalte und Fußleiste bleiben stehen; was rollt, ist
   allein der Tafelstapel (`.ef-inhalt`) — und auch nur dort, wo die
   Fensterhöhe für den längsten Reiter nicht reicht.

   ⚠️ `min-height: 0` ist dabei Pflicht: Ein Flex-Kind wird von sich aus nie
   kleiner als sein Inhalt. Ohne die Zeile wüchse der Dialog über seine
   Obergrenze hinaus, und der innere Roller spränge nie an. */
.ef {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

/* ⛔ EINE KOPFZEILE: Titel mit Marke, Chipreihe, „Zurücksetzen" (04.09.2026).
   Bis dahin stand die Chipreihe als eigener Block DARUNTER. In der Vorlage
   teilt sie sich die Zeile mit dem Titel — und das ist mehr als Gestaltung:
   Die Zeile sagt damit auf einen Blick, WAS gesetzt ist, bevor der Blick in
   die Reiter geht.

   ⚠️ `flex: 1 1 auto` an der Chipreihe und `min-width: 0`: Sie ist der
   einzige Teil, der wachsen und umbrechen darf; Titel und „Zurücksetzen"
   behalten ihre Breite, sonst rutschte der Textlink bei vielen Chips aus der
   Zeile. */
.ef-kopf {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--abstand-l);
  flex-wrap: wrap;
  margin-bottom: var(--abstand-l);
  padding-bottom: var(--abstand-m);
  border-bottom: 1px solid var(--outline-variant);
}
.ef-kopf > .ef-chips {
  flex: 1 1 auto;
  min-width: 0;
  margin-bottom: 0;
}
.ef-titel,
.ef-kopf-werkzeuge { flex: 0 0 auto; }
/* ⛔ 20px STATT 26px (17.09.2026). Das Fenster ist breiter geworden, und
   damit stand über einem 804px breiten Inhalt eine Überschrift in der Größe
   einer Seitenüberschrift. Die Schrift des ganzen Fensters ist eine Stufe
   kleiner — der Inhalt soll ruhiger wirken, nicht gedrängter; deshalb geht
   die Schrift herunter und die Breite hinauf, nicht beides in dieselbe
   Richtung.

   ⚠️ WAS SCHON AUF 12px STEHT, BLEIBT STEHEN: Marken, Reiterzahlen, Kicker,
   Skalen, der Stand am Kriterium, Fehlermeldungen und der Fußhinweis. 12px
   ist die dokumentierte Untergrenze der Skala (`theme.css`: „11px war zu
   klein"), und eine Ausnahme davon fiele hier zuerst auf, weil die Marken
   auf farbigen Flächen stehen. */
.ef-titel {
  display: flex;
  align-items: center;
  gap: var(--abstand-s);
  margin: 0;
  font-family: var(--schrift-serif);
  font-size: var(--title-large);
  font-weight: 600;
  color: var(--on-surface);
}
.ef-kopf-werkzeuge {
  display: flex;
  align-items: center;
  gap: var(--abstand-m);
  flex-wrap: wrap;
  /* Bezugsrahmen des Erklärblatts hinter dem (i) — es wird als Geschwister
     des Knopfes eingehängt (siehe `.krit-kopf`). */
  position: relative;
}
/* ⛔ HIER STAND `.ef-modus` (bis 04.09.2026) — der Umschalter
   Einfach/Erweitert im Kopf des Filterfensters. Das Fenster zeigt seit dem
   04.09.2026 immer alle Spalten: Wer „Erweiterte Filter" drückt, will die
   erweiterten Kriterien sehen und nicht erst holen. */

/* ⚠️ Schreibmaschinenschrift wie jede technische Marke der Seite. Sie trägt
   eine ABGELESENE Zahl („4 aktiv", „2 von 4 aktiv"), keine Beschriftung. */
.ef-marke {
  display: inline-flex;
  align-items: center;
  padding: 1px var(--abstand-s);
  border-radius: var(--radius-pille);
  background: var(--secondary-container);
  color: var(--on-secondary-container);
  font-family: var(--schrift-mono);
  font-size: var(--label-small);
  font-weight: 400;
  line-height: 1.5;
}
/* ⛔ Die AUS-Marke ist NEUTRAL, nicht rot: „nicht aktiv" ist kein Fehler,
   sondern ein Zustand. Ein Warnton machte aus einer Ankündigung eine
   Beanstandung. */
.ef-marke--aus {
  background: var(--surface-container-highest);
  color: var(--on-surface-variant);
}

/* ---------- Chipreihe der gesetzten Kriterien ---------- */
.ef-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--abstand-s);
  margin-bottom: var(--abstand-l);
}
.ef-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--abstand-xs);
  padding: var(--abstand-xxs) var(--abstand-xs) var(--abstand-xxs) var(--abstand-m);
  border-radius: var(--radius-pille);
  background: var(--secondary-container);
  color: var(--on-secondary-container);
  font-size: var(--body-medium);
}
/* ⚠️ 24px sind das Mindestmaß aus WCAG 2.5.8 — und mehr ist hier nicht zu
   haben, ohne dass der Chip höher wird als die Zeile, in der er steht. */
.ef-chip-weg {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pille);
  background: none;
  color: inherit;
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
}
.ef-chip-weg:hover { background: var(--zustand-hover); }
/* ⛔ HIER STAND `.ef-chips-leer` — der Satz „Noch kein Kriterium gesetzt",
   entfallen am 25.09.2026 (Bildvorlage). */

/* ---------- Reiterspalte links, Inhalt rechts (04.09.2026) ----------

   ⛔ HIER STANDEN BIS ZUM 04.09.2026 DREI NEBENEINANDERLIEGENDE SPALTEN
   (`.ef-spalten`, `.ef-spalte`, `.ef-spalte-titel`). Sie zeigten alles auf
   einmal — bei zehn Kriterien, einem Farbkreis und sechs Reglern war das eine
   Wand aus Bedienelementen. Die Bildvorlage stellt statt dessen vier Reiter
   links und den Inhalt des gewählten rechts.

   ⚠️ Die Trennung ist eine HAARLINIE zwischen den beiden Bereichen, keine
   zweite Fläche: Der Dialog trägt schon eine, und eine Fläche darin machte
   aus der Reiterspalte eine Karte in einer Karte. */
.ef-koerper {
  display: grid;
  /* ⚠️ minmax(0, …): Eine Rasterspur wird von sich aus nie schmaler als ihr
     Inhalt — ein langer Reitername oder ein Regler drückte sie sonst über den
     Dialog hinaus. Dieselbe Falle wie im Listenraster. */
  /* ⛔ 260px SEIT DEM 17.09.2026 (abends) — die zweizeilige Kachel braucht
     sie. Ihre untere Zeile misst „Laden" (44) + 8 + „Überschreiben" (77) +
     8 + Mülleimer (44) = **181px**; davon gehen Polster (2 × 8), Kante (2)
     und der Rollbalken der Satzliste (15) ab. Bei 220px blieben 187px — sechs
     Pixel Reserve, also keine: Ein längeres Wort, eine andere Ersatzschrift,
     und die Zeile läuft über. Bei 260px sind es 227px.

     ⚠️ DER PREIS STEHT IM FARBKREIS-REITER, und er ist gemessen: Er stapelt
     unter 720px Inhaltsbreite, und die ist `Dialogbreite − 96 − Spalte`. Mit
     260px braucht er ein Fenster ab **1121px** statt ab 1081px — das Band
     dazwischen verliert seine zweite Spalte. Unter 1024px stapelte er
     ohnehin schon. Der Reiter wird dort höher, das Fenster nicht: Seine
     Höhe ist bei 780px gedeckelt, es rollt innen etwas mehr. */
  grid-template-columns: minmax(0, 260px) minmax(0, 1fr);
  gap: var(--abstand-xl);
  flex: 1 1 auto;
  min-height: 0;
  /* ⛔ EIN ROLLBALKEN FÜR DEN GANZEN KÖRPER (25.09.2026). Bis dahin rollten
     Reiterspalte, Tafelstapel und Satzliste jede für sich — im Fenster
     standen dadurch zwei, drei Rollbalken nebeneinander, einer davon in der
     Satzliste IN der rollenden Spalte. Jetzt rollt allein der Körper; Kopf
     und Fuß bleiben stehen, und die Reiterspalte haftet oben (`.ef-seite`). */
  overflow-y: auto;
  /* ⛔ `max-content`, NICHT `auto`: In einem Körper mit fester Höhe schrumpft
     der Browser automatische Zeilen unter ihren Inhalt, sobald ein Kind
     selbst ein Rollbehälter ist (die waagerechte Reiterleiste unter 900px
     fiel gemessen auf 3px Höhe). Jede Zeile ist so hoch wie ihr Inhalt —
     gerollt wird der Körper. */
  grid-auto-rows: max-content;
}
.ef-seite {
  display: flex;
  flex-direction: column;
  /* Der Block der gespeicherten Filter sitzt unter den Reitern — dazwischen
     eine Linie, keine große Lücke. */
  gap: var(--abstand-l);
  min-width: 0;
  /* ⚠️ Die Spalte HAFTET oben, solange sie kürzer ist als der Tafelstapel:
     Beim Rollen durch einen langen Reiter bleiben die Reiter erreichbar. Ist
     sie selbst die längere (aufgeklappte Satzliste), bestimmt sie die
     Zeilenhöhe und rollt schlicht mit — ohne eigenen Rollbalken. */
  align-self: start;
  position: sticky;
  top: 0;
}
/* ⛔ DER TAFELSTAPEL: ALLE REITER LIEGEN ÜBEREINANDER IN EINER RASTERZELLE.
   Das ist die ganze Antwort auf „das Fenster ändert beim Reiterwechsel seine
   Größe“ — eine Rasterzeile ist so hoch wie ihr höchstes Kind, also ist der
   Stapel immer so hoch wie der längste Reiter, gleich welcher offen ist.
   Gemessen wird dabei NICHTS: Der Browser rechnet die Höhe in jeder Lage neu,
   eine in JavaScript festgehaltene Zahl wäre beim nächsten Umbruch falsch.

   ⚠️ Die Linie hängt jetzt am Körper statt am Inhalt (align-items). Das ist
   seit dem Stapel kein Unterschied mehr: Die Höhe ist eine Konstante, und
   eine Kante, die über die ganze Höhe läuft, liest sich als Trennung statt
   als Rest. */
/* ⛔ KEIN `min-height: 0` MEHR (25.09.2026). Es gehörte zum eigenen
   Rollbehälter: Seit der Körper rollt, drückte es die Rasterzeile unter die
   Höhe der Tafel — gemessen bei 400px 466px Zeile für 637px Inhalt, und der
   Block der gespeicherten Filter lag über den Farbchips. */
.ef-inhalt {
  min-width: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  /* ⚠️ Kein eigener Rollbehälter mehr (25.09.2026) — gerollt wird der
     Körper, siehe `.ef-koerper`. */
  padding-left: var(--abstand-xl);
  border-left: 1px solid var(--outline-variant);
  /* Die Farbspalten darin richten sich nach DIESER Breite, nicht nach dem
     Fenster (siehe .farb-raster). */
  container-type: inline-size;
  container-name: ef-inhalt;
}
.ef-tafel {
  grid-area: 1 / 1;
  min-width: 0;
}
/* ⛔ `visibility: hidden` UND NICHT `display: none`. Der ruhende Reiter soll
   seinen Platz behalten — er ist der Grund, warum das Fenster seine Höhe hält.
   `display: none` nähme genau diesen Platz weg. Zugleich ist er damit weder
   fokussierbar noch für Vorlesehilfen da; der Weg dorthin verschließt sich
   nicht, denn der Reiterknopf steht daneben. Dieselbe Technik und dieselbe
   Begründung wie bei der Stückliste auf /wissen. */
.ef-tafel--ruht { visibility: hidden; }
.ef-tafel:focus-visible { outline-offset: 2px; }

/* ---------- Die Reiter ---------- */
.ef-reiter {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-xxs);
}
.ef-reiter-knopf {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--abstand-s);
  width: 100%;
  padding: var(--abstand-m) var(--abstand-l);
  border: 1px solid transparent;
  border-radius: var(--radius-m);
  background: none;
  color: var(--on-surface-variant);
  font-family: inherit;
  /* Eine Stufe größer seit der Bildvorlage vom 25.09.2026 — die Reiter
     tragen jetzt den Kopf der Spalte allein. */
  font-size: var(--title-medium);
  text-align: left;
  cursor: pointer;
  transition: background var(--dauer-kurz), color var(--dauer-kurz);
}
.ef-reiter-knopf:hover { background: var(--zustand-hover); }
/* ⛔ DER AKTIVE REITER TRÄGT FLÄCHE, KANTE UND FARBE — nicht nur Farbe. Auf
   einer fast schwarzen Seite ist eine gefüllte Fläche für sich schwach
   (1,10:1 gegen die Karte); die Kante trägt die Abgrenzung, genau wie bei den
   Karten der Seite. Dieselbe Regel wie beim Filter-Chip: Der gewählte darf
   nicht WENIGER erkennbare Form haben als der nicht gewählte. */
.ef-reiter-knopf--aktiv {
  background: var(--secondary-container);
  border-color: var(--outline);
  color: var(--on-secondary-container);
  font-weight: 500;
}
.ef-reiter-name {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Die Zahl gesetzter Kriterien — eine ABGELESENE Zahl, also Monospace, wie
   jede technische Marke der Seite. Eine runde Pille wie in der Bildvorlage.

   ⚠️ SEIT DEM 25.09.2026 STEHT SIE NUR BEI GESETZTEN KRITERIEN (Bildvorlage).
   Die Null ist für Vorlesehilfen als Wort da (js/seite-liste.js); sichtbar
   sagt der fehlende Kreis „hier ist nichts gesetzt". Die Zeile springt
   nicht, weil der Name links steht und nicht mit der Pille wächst. */
.ef-reiter-zahl {
  flex: 0 0 auto;
  min-width: 26px;
  padding: 1px var(--abstand-xs);
  border-radius: var(--radius-pille);
  background: var(--surface-container-highest);
  color: var(--on-surface);
  font-family: var(--schrift-mono);
  font-size: var(--label-medium);
  line-height: 24px;
  text-align: center;
}
/* ⛔ HIER STANDEN `.ef-reiter-zahl--leer` und `.ef-reiterhinweis` — beide am
   25.09.2026 mit der Bildvorlage entfallen. */

/* ---------- Gruppenüberschriften im Reiter ----------

   Serif wie jede Überschrift der Seite, daneben der Stand in Monospace —
   „1 gewählt", „Blau gewählt". Das eine BENENNT, das andere ist ABGELESEN;
   der Familienwechsel trägt genau diesen Unterschied. */
/* ⚠️ GROTESK STATT SERIF SEIT DEM 25.09.2026 (Bildvorlage des Farbreiters,
   auf alle Reiter übertragen). Das Fenster ist EINE Bedienfläche wie die
   Kopfleiste; die Serif trägt darin allein den Titel „Filter". Dieselbe
   Abwägung wie bei der Kopfleiste: Die Fläche wiegt schwerer als die Regel
   „was gliedert, steht in Serif". */
.ef-inhalt .krit-kopf h3 {
  font-family: var(--schrift);
  font-size: var(--title-medium);
  font-weight: 600;
  color: var(--on-surface);
}
/* Im Fenster bleibt das (i) neben dem Schalter in derselben Zeile — die
   aufklappende Erklärung, für die die Zeile umbrechen muss, gibt es hier
   nicht (Popup-Fassung, `infoHtml`). */
.ef .krit-kopf--schalter { flex-wrap: nowrap; }

/* ---------- Die geteilten Bausteine sind IM FENSTER eine Stufe kleiner ----

   ⛔ ÜBER `.ef <Baustein>` UND NICHT AN DER GRUNDREGEL. Chip, Schalterzeile,
   Suchfeld und Reglerwert stehen auch in der Filterspalte, auf der
   Druckerseite und in der Bauartzeile; ihre Grundgröße dort ist unberührt.
   Eine gesenkte Grundregel hätte vier Ansichten verkleinert, von denen drei
   niemand angeschaut hat — und das fiele erst auf, wenn jemand dort
   hinsieht.

   ⚠️ Zwei Klassen wiegen schwerer als eine: Die Grundregeln stehen an
   `.chip`, `.schalter-zeile` und `.bereich-werte` (je 0,1,0), diese hier an
   `.ef …` (0,2,0). `.filter-suchfeld` setzt gar keine Größe und erbt sonst
   die 16px des Fließtexts (`button, input { font: inherit }`). */
.ef .chip { font-size: var(--body-small); }
.ef .schalter-zeile { font-size: var(--title-small); }
.ef .filter-suchfeld { font-size: var(--body-medium); }
.ef .bereich-werte { font-size: var(--body-small); }
.krit-stand {
  font-family: var(--schrift-mono);
  font-size: var(--label-medium);
  color: var(--on-surface-variant);
}
/* Zwischen zwei Kriteriengruppen eine Haarlinie — in der Vorlage trennt sie
   „Material" von „Farbe". */
.ef-inhalt > .sheet-abschnitt + .sheet-abschnitt {
  margin-top: var(--abstand-xl);
  padding-top: var(--abstand-xl);
  border-top: 1px solid var(--outline-variant);
}

/* ---------- Ein ausgeschaltetes Kriterium ---------- */

/* ⛔ AUSGEGRAUT, ABER NICHT UNBEDIENBAR — und seit dem 04.09.2026 ist das
   BEDIENELEMENT SELBST der Weg zurück. Bis dahin verbarg ein nicht gesetztes
   Kriterium seinen Regler und zeigte statt dessen einen Satz und den Knopf
   „Filter aktivieren"; jetzt steht der Regler da, gedämpft, und wer ihn
   anfaßt, hat das Kriterium damit scharfgestellt.

   ⚠️ GEDÄMPFT WIRD ÜBER FARBROLLEN, NICHT ÜBER `opacity`. Eine Deckkraft von
   0,5 legte sich auch über den Fokusring und über die Kante der Spur — und
   die trägt nach WCAG 1.4.11 ihre 3:1, die man nicht halbieren darf. Gedämpft
   ist deshalb, was Aussage ist (Überschrift, Wertzeile, Skala), und die
   Füllung; Kante, Griff und Fokusring bleiben, wie sie sind.

   ⛔ KEIN `pointer-events: none` und kein `disabled`. Der ausgegraute Zustand
   ist eine Aussage über die WIRKUNG, keine Sperre — genau hier führt der Weg
   hinein. */
.ef-aus .krit-kopf h3,
.ef-aus .bereich-werte,
.ef-aus .regler-skala { color: var(--on-surface-variant); }
/* Die Füllung zwischen den Griffen tritt zurück; Spur, Kante und Griffe
   bleiben unverändert sichtbar, sonst wäre der Regler nicht mehr zu
   erkennen — und er ist das, was hier bedient werden soll. */
.ef-aus .bereich-fuell { background: var(--outline); }
.ef-aus .bereich-griff::-webkit-slider-thumb { background: var(--outline); }
.ef-aus .bereich-griff::-moz-range-thumb { background: var(--outline); }
/* Der einwertige Toleranzregler: derselbe gedämpfte Griff. Je Engine eine
   eigene Regel — in einer Liste verwürfen beide Browser die ganze Regel. */
.ef-aus .regler input[type="range"]::-webkit-slider-thumb { background: var(--outline); }
.ef-aus .regler input[type="range"]::-moz-range-thumb { background: var(--outline); }

/* ---------- Das Erklärblatt hinter dem (i) (04.09.2026) ----------

   ⛔ NUR IM FILTERFENSTER. In der 420px schmalen Filterspalte bleibt die
   aufklappende Zeile: Dort ist eine schwebende Blase am 10.08.2026 daran
   gescheitert, daß sie nirgends ausweichen konnte und gemessen 11 von 11
   Bedienelementen verdeckte. Das Fenster ist bis zu 1180px breit und
   dreispaltig — derselbe Einwand trifft dort nicht zu. */
.krit-blase {
  position: absolute;
  top: calc(100% + var(--abstand-xxs));
  left: 0;
  z-index: 2;
  width: max-content;
  /* ⛔ NIE BREITER ALS SEINE SPALTE. 320px lesen sich gut, aber bei 1024px
     Fenster ist eine der drei Spalten nur rund 300px breit — gemessen ragten
     dort 4 von 10 Blättern über den Dialogrand, und der Dialog rollt
     waagerecht nicht. Die zweite Grenze bindet die Breite an den
     Bezugsrahmen (.krit-kopf, also die Spalte) und macht den Überlauf
     baulich unmöglich statt ihn je Breite nachzurechnen. */
  max-width: min(320px, 100%);
  padding: var(--abstand-m);
  border-radius: var(--radius-m);
  background: var(--surface-container-highest);
  /* Es liegt ÜBER dem Inhalt und ist damit eine echte Überlagerung — hier
     gilt die Schattenregel, anders als bei den Karten. */
  box-shadow: var(--schatten-schwebend);
  border: 1px solid var(--outline);
}
.krit-blase-text {
  margin: 0 0 var(--abstand-s);
  font-size: var(--body-small);
  color: var(--on-surface);
}
.krit-blase-zu { padding-inline: 0; }
/* Das Blatt hinter dem (i) im KOPF des Filterfensters (25.09.2026) steht am
   rechten Rand: Die Werkzeuggruppe ist zu schmal, um es in ihrer Breite zu
   führen, und links davon läge es über den Chips. Es bleibt im Fenster —
   rechtsbündig an der Gruppe, die selbst am rechten Rand steht. */
.ef-kopf-werkzeuge .krit-blase {
  left: auto;
  right: 0;
  max-width: min(320px, 80vw);
}

/* ---------- Fassung mit Titelzeile (Kachelzeichen, 23.09.2026) ----------
   Titel links, × rechts, darunter der Grund. Das × ist eine 44px-Tippfläche,
   die oben und rechts ins Polster ragt, damit die Zeile niedrig bleibt.
   ⚠️ NICHT nach unten: Dort läge sie über der ersten Textzeile, und ihr
   Fokusrahmen verdeckte den Grund (gesehen im Bildschirmabzug). Der Punkt vor dem Titel wiederholt die Farbe des
   Zeichens; die Aussage trägt das Wort im Titel. */
.krit-blase-kopf {
  display: flex;
  align-items: center;
  gap: var(--abstand-s);
}
.krit-blase-titel {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-size: var(--body-medium);
  font-weight: 600;
  color: var(--on-surface);
}
.krit-blase-titel::before {
  content: "";
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: var(--abstand-s);
  border-radius: 50%;
  vertical-align: middle;
  background: var(--outline);
}
.krit-blase--inkompatibel .krit-blase-titel::before { background: var(--error); }
.krit-blase--eingeschraenkt .krit-blase-titel::before { background: var(--warnung-erhoeht); }
.krit-blase--kompatibel .krit-blase-titel::before { background: var(--balken-gruen); }
.krit-blase-x {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin: calc(-1 * var(--abstand-m)) calc(-1 * var(--abstand-m)) 0 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--on-surface-variant);
  cursor: pointer;
}
.krit-blase-x svg { width: 16px; height: 16px; }
.krit-blase--titel .krit-blase-text {
  margin: var(--abstand-xs) 0 0;
  font-size: var(--body-medium);
  color: var(--on-surface-variant);
}
/* ⛔ DER BEZUGSRAHMEN IST DIE KOPFZEILE, NICHT DER (i)-KNOPF — und das ist
   nicht die naheliegende Wahl, sondern die einzige, die funktioniert. Das
   Blatt wird als GESCHWISTER des Knopfes eingehängt; ein `position: relative`
   am Knopf selbst macht ihn deshalb nicht zum Bezugsrahmen seines
   Nachbarelements. Gemessen stand das Blatt dadurch bei y=1120 in einem
   1105px hohen Fenster, also vollständig unterhalb des Sichtbereichs: Der
   nächste positionierte Vorfahre war der Dialog, und `top: 100%` bezog sich
   auf DESSEN Höhe.

   ⚠️ Es steht damit unter der ganzen Kopfzeile statt genau unter dem Symbol.
   Das ist der Preis und zugleich ein Gewinn: Die Kopfzeile bricht um, und ein
   am Symbol ausgerichtetes Blatt begänne je nach Umbruch woanders. */
.krit-kopf { position: relative; }

/* ---------- Benannte Filtersätze (04.09.2026) ----------

   ⛔ HIER STAND `.ef-vorgabe` — der Block für die EINE Voreinstellung. Jetzt
   trägt derselbe Platz eine Liste benannter Sätze; die Überschrift heißt seit
   dem 17.09.2026 „GESPEICHERTE FILTER“ und die vier Wortlinks je Zeile sind
   drei Symbole plus ein Stift am Namen.

   ⚠️ Er steht unten in der Reiterspalte, wie in der Vorlage — nicht im
   Inhaltsbereich: Er gehört zu keinem Reiter, sondern zum ganzen Filter. */
.ef-saetze {
  padding-top: var(--abstand-s);
  border-top: 1px solid var(--outline-variant);
}
/* ⛔ DIE ÜBERSCHRIFT IST DER SCHALTER (25.09.2026, Bildvorlage): „Gespeicherte
   Filter · 3 ▾" als eine Zeile, zugeklappt als Vorgabe. Aufgeklappt steht die
   Liste im Fluss darunter. 44px hoch wie jedes Ziel der Spalte. */
.ef-saetze-schalter {
  display: flex;
  align-items: center;
  gap: var(--abstand-s);
  width: 100%;
  min-height: 44px;
  padding: 0 var(--abstand-l);
  border: 0;
  border-radius: var(--radius-m);
  background: none;
  color: var(--on-surface-variant);
  font-family: inherit;
  font-size: var(--body-medium);
  text-align: left;
  cursor: pointer;
}
.ef-saetze-schalter:hover { background: var(--zustand-hover); }
.ef-saetze-kicker { flex: 1 1 auto; min-width: 0; }
.ef-saetze-zahl {
  font-family: var(--schrift-mono);
  font-size: var(--label-medium);
}
.ef-saetze-pfeil {
  font-size: var(--label-medium);
  transition: transform var(--dauer-kurz);
}
.ef-saetze-schalter[aria-expanded="true"] .ef-saetze-pfeil { transform: rotate(180deg); }
.ef-saetze-inhalt { margin-top: var(--abstand-s); }
.ef-saetze-inhalt[hidden] { display: none; }
.ef-saetze-leer,
.ef-saetze-hinweis {
  margin: 0;
  font-size: var(--label-medium);
  color: var(--on-surface-variant);
}
.efs-liste {
  list-style: none;
  margin: 0 0 var(--abstand-m);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--abstand-s);
  /* ⛔ KEIN EIGENER ROLLBEHÄLTER MEHR (25.09.2026). Hier stand
     `max-height: 300px; overflow-y: auto` — ein zweiter Rollbalken innerhalb
     der rollenden Spalte. Die Liste steht jetzt im Fluss und rollt mit dem
     Körper des Fensters; zugeklappt nimmt der Block eine Zeile ein. */
}
.efs-eintrag {
  /* ⚠️ Nur seitlich gepolstert: Die beiden 44px-Zeilen bringen ihre Luft
     oben und unten selbst mit (siehe `.efs-kopf`). */
  padding: 0 var(--abstand-s);
  border: 1px solid var(--outline-variant);
  border-radius: var(--radius-m);
  background: var(--surface-container-high);
}
/* Der Satz, dem die laufende Auswahl entspricht — Kante UND Fläche, nicht
   Farbe allein. */
.efs-eintrag--aktiv {
  border-color: var(--outline);
  background: var(--secondary-container);
}
/* ⛔ ZWEI ZEILEN, UND ZWAR HÖCHSTENS ZWEI (17.09.2026, abends). Oben Name und
   Stift, unten die beiden Wortlinks und der Mülleimer. Beide Zeilen sind
   **44px hoch**, und das ist keine Großzügigkeit, sondern die Bedingung dafür,
   dass die zwei Symbole am rechten Rand einander nicht überlappen — siehe die
   Rücknahme an `.efs-stift`.

   ⛔ `flex-wrap: nowrap` IN BEIDEN ZEILEN. Mit Umbruch wurde aus der Kachel
   je nach Name eine dritte und vierte Zeile; gemessen stand ein langer Name
   in einer **110,8px** hohen Kachel. Jetzt ist sie in jeder Lage 90px hoch.

   ⚠️ Kein Polster oben und unten am Eintrag: Die beiden 44px-Zeilen bringen
   ihre Luft selbst mit. Ein zusätzliches Polster addierte sich darauf und
   machte die Kachel wieder höher, ohne ein Ziel zu vergrößern. */
.efs-kopf {
  display: flex;
  align-items: center;
  gap: var(--abstand-xs);
  flex-wrap: nowrap;
  min-height: 44px;
}
/* ---------- Der Name, an Ort und Stelle bearbeitbar ----------

   ⛔ DAS FELD STEHT GENAU DA, WO DER NAME STAND, und es übernimmt seine
   Maße: Es ist das einzige Feld des Blocks, dessen Breite nicht feststeht,
   sondern von den Marken daneben übrigbleibt. */
.efs-name-feld {
  flex: 1 1 auto;
  min-width: 0;
  box-sizing: border-box;
  padding: 1px var(--abstand-xs);
  border: 1px solid var(--primary);
  border-radius: var(--radius-s);
  background: var(--surface-container-lowest);
  color: var(--on-surface);
  font-family: inherit;
  font-size: var(--body-small);
}
/* ⛔ DER STIFT SITZT AM RECHTEN ENDE DER NAMENSZEILE — OHNE NEGATIVE RÄNDER.
   Hier stand bis zum 17.09.2026 eine Rücknahme (`margin-block: -16px`), die
   seine 44px auf 24px Zeilenbeitrag zusammenzog, damit die Kachel niedrig
   bleibt. Gemessen war das ein echter Fehler: Stift und Mülleimer stehen
   beide am rechten Rand, und ihre Tippflächen überlappten sich um
   **40 × 19,4px** — ein Tipp dort traf `elementFromPoint` zufolge den
   **Mülleimer**. Wer umbenennen wollte, bekam die Löschabfrage.

   ⛔ ZWEI GESTAPELTE 44px-ZIELE BRAUCHEN 88px, und daran führt nichts
   vorbei: Jede Rücknahme, die die Kachel darunter drückt, schiebt die beiden
   Flächen ineinander. Die Zeilen sind deshalb 44px hoch und die Ränder null.

   ⚠️ An den rechten Rand kommt der Stift ohne Auto-Rand: Der Name davor
   trägt `flex: 1 1 auto` und füllt die Zeile. */
/* ⛔ DER NAME BRICHT NICHT UM, ER WIRD GEKÜRZT. Ein Umbruch war die einzige
   Stelle, an der die Kachel über zwei Zeilen hinauswachsen konnte; das
   frühere `overflow-wrap: anywhere` machte aus einem langen Namen gemessen
   41,6px Text in einer 110,8px hohen Kachel.

   ⚠️ Der vollständige Name geht dabei nicht verloren — er steht im `title`
   und erscheint beim Überfahren. `text-overflow` allein sagt nur, DASS
   gekürzt wurde, nicht was dastand. */
.efs-name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--body-small);
  color: var(--on-surface);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.efs-eintrag--aktiv .efs-name { color: var(--on-secondary-container); }
/* ⛔ HIER STAND `.efs-marke` — die Versalienpille „AKTIV" am Satz, dem die
   laufende Auswahl entspricht. Sie ist am 17.09.2026 ERSATZLOS ENTFALLEN:
   Den Zustand trägt die Kachel selbst, über Fläche UND Kante
   (`.efs-eintrag--aktiv`), und in einer zweizeiligen Kachel nahm die Pille
   dem Namen den Platz, um dasselbe ein zweites Mal zu sagen.

   ⚠️ DIE AUSSAGE GEHT DABEI NICHT VERLOREN. „Farbe allein trägt in diesem
   Projekt keine Aussage" gilt weiter — sichtbar ist es jetzt Fläche und
   Kante, für Vorlesehilfen steht das Wort als `.sr-only` in der Kachel. Wer
   die sr-only-Zeile entfernt, nimmt die Auskunft wirklich weg. */
/* ---------- Zwei Wortlinks und ein Symbol (17.09.2026, nachmittags) --------

   ⚠️ Der folgende Block gehört zur Wegereihe der ZWEITEN Zeile. Der
   START-Schalter, der bis zum 24.09.2026 in der ersten stand, ist entfallen.

   ⛔ LADEN UND ÜBERSCHREIBEN SIND WIEDER WÖRTER. Als Ordner und Diskette
   standen sie nebeneinander und waren nicht auseinanderzuhalten — beide
   sagen „etwas mit einer abgelegten Sache", und welche Richtung gemeint ist,
   sagt das Motiv nicht. `title` und `aria-label` beschriften sie zwar, aber
   ein Hinweis, den man abwarten muss, kommt für eine Wahl zwischen zwei
   Nachbarn zu spät.

   ⛔ LÖSCHEN BLEIBT EIN SYMBOL, und das ist kein Widerspruch: Der Mülleimer
   steht für sich allein am rechten Ende und konkurriert mit nichts. Er ist
   zugleich der einzige Weg der Zeile, den man nicht zurücknehmen kann — ein
   Wort zwischen zwei anderen Wörtern wäre dort die unauffälligste Fassung.
   Dasselbe gilt für den Stift am Namen.

   ⚠️ Die Reihe bricht NICHT um (`flex-wrap: nowrap`). Gemessen passen
   „Laden" und „Überschreiben" bei 12px samt Mülleimer in die 220px-Spalte;
   ein Umbruch entstünde erst bei einem vierten Weg — und dann soll er
   auffallen, statt still eine zweite Reihe aufzuziehen. */
.efs-aktionen {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  /* ⚠️ 8px statt 16: Die Wortlinks sind jetzt selbst 44px breite Ziele, ihr
     Text steht darin mittig — der sichtbare Abstand zwischen „Laden" und
     „Überschreiben" ist damit 8 + 6 = 14px und nicht 22. */
  gap: var(--abstand-s);
  min-height: 44px;
}
/* ⛔ 44 × 44px AUCH BEIM WORT, nicht nur beim Symbol. Ein Wortlink bringt
   weder Höhe noch Breite von selbst mit: „Laden" misst bei 12px gemessen
   32 × 16px. `min-height` gibt ihm die Zeilenhöhe, `min-width` die Breite —
   der Text sitzt mittig darin und sieht unverändert aus. */
.efs-weg {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  min-width: 44px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--primary);
  font-family: inherit;
  font-size: var(--label-medium);
  cursor: pointer;
  text-decoration: underline;
}
.efs-weg:hover { text-decoration: none; }
/* ⛔ AUF DER AKTIV-FLÄCHE EINE ANDERE TINTE — gemessen, nicht vermutet:
   `--primary` trägt auf `--secondary-container` nur 3,06:1, und ein Wortlink
   braucht als Text seine 4,5:1. Dieselbe Rolle und derselbe Grund wie beim
   Symbol darunter. */
.efs-eintrag--aktiv .efs-weg { color: var(--on-secondary-container); }
/* ⛔ 44 × 44px TIPPFLÄCHE BEI 20px SYMBOL. Das Maß ist das des Burgers und
   liegt weit über den 24px aus WCAG 2.5.8; bei einem Löschen-Knopf ist ein
   knappes Ziel kein Schönheitsfehler. */
.efs-aktion {
  width: 44px;
  height: 44px;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: var(--radius-m);
  background: none;
  color: var(--primary);
  font-family: inherit;
  cursor: pointer;
}
/* ⛔ DER MÜLLEIMER STEHT AM RECHTEN ENDE, abgesetzt von den beiden Wörtern.
   Zwischen ihnen wäre der eine Weg, den man nicht zurücknehmen kann, der
   unauffälligste der Zeile.

   ⛔ OHNE JEDEN NEGATIVEN RAND. Senkrecht zogen sie seine Fläche in die des
   Stifts darüber (siehe dort); waagerecht ließen sie ihn gemessen **4px über
   den Inhaltskasten der Zeile** hinausragen — und `.efs-liste` rollt
   senkrecht, was ihr Überlaufverhalten waagerecht von `visible` auf `auto`
   hebt: aus 4px Bleed wird dort ein waagerechter Rollbalken. Die 8px Polster
   der Kachel reichen völlig; das Symbol steht mit 30px Abstand zur Kante. */
.efs-aktionen .efs-aktion { margin-left: auto; }
.efs-aktion svg {
  width: 20px;
  height: 20px;
  /* Ein Symbol ist ein grafisches Objekt und trägt hier die ganze Aussage —
     es darf nicht gedämpft werden. */
  flex: 0 0 auto;
}
/* ⚠️ Die Rückmeldung kommt aus der Zustandsebene, nicht aus der
   Flächenrampe: Der Eintrag steht selbst schon auf `-high`, und „eine Stufe
   weiter" wäre dort in beiden Richtungen unsichtbar (11.08.2026). */
.efs-aktion:hover { background: var(--zustand-hover); }
.efs-aktion:active { background: var(--zustand-aktiv); }
/* ⛔ AUF DER AKTIV-FLÄCHE EINE ANDERE TINTE — gemessen, nicht vermutet:
   `--primary` trägt auf `--secondary-container` nur 3,06:1. Ein Symbol ist ein
   grafisches Objekt und braucht seine 3:1 (WCAG 1.4.11), es stände also
   formal knapp darüber — und wäre damit genau die Paarung, welche die
   nächste Flächenänderung kippt. Dieselbe Rolle und derselbe Grund wie beim
   Namen eine Regel weiter oben: Wer eine Fläche ändert, ändert jeden Text und
   jedes Zeichen darauf mit. */
.efs-eintrag--aktiv .efs-aktion { color: var(--on-secondary-container); }

/* Das Formular einer Zeile bzw. für einen neuen Satz. */
.efs-form {
  margin-top: var(--abstand-s);
  padding: var(--abstand-m);
  border: 1px solid var(--outline);
  border-radius: var(--radius-m);
  background: var(--surface-container-highest);
}
.efs-feld { display: block; }
.efs-feld input {
  width: 100%;
  box-sizing: border-box;
}
.efs-frage {
  margin: 0 0 var(--abstand-s);
  font-size: var(--body-small);
  color: var(--on-surface);
}
.efs-fehler {
  margin: var(--abstand-xs) 0 0;
  font-size: var(--label-medium);
  color: var(--error);
}
.efs-formknoepfe {
  display: flex;
  flex-wrap: wrap;
  gap: var(--abstand-s);
  margin-top: var(--abstand-s);
}
.ef-saetze-fuss {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-s);
  align-items: flex-start;
}

/* ---------- Fußleiste ---------- */
.ef-fuss {
  /* ⛔ SIE STEHT SEIT DEM 09.09.2026 AUSSERHALB DER TAFELN — sie gehört zu
     keinem Reiter. Im Stapel stünde sie viermal im Dokument, und auf einem
     kurzen Reiter säße sie mitten in der Fläche statt am Fuß des Fensters. */
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--abstand-m);
  flex-wrap: wrap;
  margin-top: var(--abstand-xl);
  padding-top: var(--abstand-l);
  border-top: 1px solid var(--outline-variant);
}
/* ⛔ HIER STAND `.ef-fuss-hinweis` — der Satz neben dem Anwenden-Knopf steht
   seit dem 25.09.2026 hinter dem (i) im Kopf (Bildvorlage). */

/* ---------- Schmale Fenster: die Reiter werden eine Leiste ----------

   ⛔ AUS DER SPALTE WIRD EINE WAAGERECHTE REITERLEISTE ÜBER DEM INHALT. Eine
   220px breite Spalte neben einer 300px breiten Inhaltsfläche wäre kein
   Layout mehr, sondern zwei zu schmale Spalten. Die Reihenfolge im Markup
   bleibt dieselbe — es ist ein Raster, keine Umordnung per `order`. */
@media (max-width: 899px) {
  .ef-koerper {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--abstand-l);
  }
  /* ⚠️ Der Block der gespeicherten Filter wandert dabei ANS ENDE: Über dem Inhalt
     stünde zwischen Reitern und Kriterien eine Liste, die mit beidem nichts
     zu tun hat. Das Raster ordnet ihn um, ohne das Markup anzurühren. */
  .ef-seite { display: contents; }
  .ef-reiter {
    flex-direction: row;
    /* Vier Reiter passen unter 400px nicht nebeneinander — sie rollen
       waagerecht, statt in zwei Zeilen umzubrechen: Eine Reiterleiste mit
       zweiter Zeile liest sich als zwei Gruppen. */
    overflow-x: auto;
    /* ⚠️ Ohne sichtbaren Rollbalken (25.09.2026): Unter der Reiterleiste
       stand sonst ein zweiter, waagerechter Balken neben dem des Körpers.
       Gerollt wird weiter mit Finger, Umschalt+Rad und den Pfeiltasten
       (ffVerbindeReiter), die den Reiter ins Bild holen. */
    scrollbar-width: none;
    gap: var(--abstand-xs);
    padding-bottom: var(--abstand-xxs);
    border-bottom: 1px solid var(--outline-variant);
  }
  .ef-reiter::-webkit-scrollbar { display: none; }
  .ef-reiter-knopf {
    width: auto;
    flex: 0 0 auto;
    padding: var(--abstand-s) var(--abstand-m);
    font-size: var(--body-medium);
  }
  /* Die senkrechte Trennung ergäbe untereinander keinen Sinn — die Leiste
     trägt ihre eigene Kante darüber. */
  /* ⛔ UNTEREINANDER ROLLT DER KÖRPER, NICHT DER STAPEL. Reiterleiste, Tafel
     und Voreinstellungen sind hier drei Rasterzeilen einer Spalte; läßt man
     allein die mittlere rollen, drückte sie die Satzliste aus dem Fenster.
     Kopfzeile und Fußleiste bleiben auch hier stehen. Der Stapel selbst und
     damit die Höhenzusage ändern sich nicht. */
  .ef-inhalt {
    border-left: 0;
    padding-left: 0;
  }
  .ef-saetze { order: 1; }
  .dialog:has(.ef) { padding: var(--abstand-l); }
  /* Der Anwenden-Knopf trägt die Trefferzahl und ist der Abschluss — auf
     schmalen Fenstern bekommt er die ganze Zeile. */
  .ef-fuss > .knopf { flex: 1 1 100%; }
}

/* ==================================================================
   SEITENKOPF DER LISTEN (Bildvorlage 1, 03.09.2026)
   ==================================================================

   Brotkrumen, Titel, ein Satz — über dem Layout, also über Filterspalte und
   Liste zugleich. Bis dahin trug die Liste nur ein sr-only-<h1>. */
.listen-seitenkopf {
  margin-bottom: var(--abstand-xl);
}
.listen-titel {
  margin: var(--abstand-s) 0 0;
  font-family: var(--schrift-serif);
  /* Wächst mit dem Fenster wie jeder Seitentitel des Projekts — auf 320px
     sind 44px nicht zu haben. */
  font-size: clamp(28px, 4vw, 44px);
  font-weight: 600;
  line-height: var(--zeile-eng);
  color: var(--on-surface);
}
.listen-einleitung {
  margin: var(--abstand-s) 0 0;
  /* Der Satz ist Fließtext und braucht eine Zeilenbegrenzung — über die volle
     Breite einer 1920px-Seite wären es 200 Zeichen je Zeile. */
  max-width: var(--lesebreite);
  font-size: var(--body-medium);
  color: var(--on-surface-variant);
}

/* ---------- Die Zeile unter der Liste ---------- */
.nachlade-zeile {
  display: flex;
  align-items: center;
  gap: var(--abstand-l);
  flex-wrap: wrap;
  margin-top: var(--abstand-xl);
}
.nachlade-hinweis {
  margin: 0;
  font-size: var(--label-medium);
  color: var(--on-surface-variant);
}
/* Ohne Knopf steht der Hinweis allein — dann gehört er nicht an den linken
   Rand geklebt, sondern dorthin, wo der Knopf war. */
.nachlade-zeile > .nachlade-hinweis:only-child { margin-inline: auto; }
.nachlade-hinweis:focus-visible { outline: 2px solid var(--primary); }

/* ---------- Versandzeile auf Kachel und in der Tabelle (18.09.2026) --------

   ⚠️ HIER STANDEN .kachel-marken UND .zustandsmarke. Beide hatten genau EINEN
   Leser — die Marke „Versandfrei" auf der Kachel —, und der ist in
   PD.komponenten.versandZeileHtml aufgegangen. Ein Stylesheet, das Klassen
   beschreibt, die kein Element mehr trägt, liest sich beim nächsten Mal wie
   ein vorhandener Baustein.

   ⛔ DIE GRUNDFASSUNG IST DIE DES DESKTOPS, und sie ist unverändert: Sichtbar
   ist allein „Versandfrei", in der Pillenform der früheren Zustandsmarke. Die
   beiden anderen Fassungen stehen im Markup und sind hier ausgeblendet; sie
   erscheinen erst in der mobilen Fassung weiter unten.

   ⚠️ Schreibmaschinenschrift wie jede technische Marke der Seite. Der grüne
   Ton ist --balken-gruen: Er ist im Projekt die einzige Grünrolle, die als
   TEXT geprüft ist — ein hellerer Grünton läge unter der Schwelle. */
.versandzeile {
  /* ⚠️ `block` mit `width: max-content` und NICHT `inline-flex`. Die frühere
     Marke steckte in einer Hülle auf Blockebene; als Inline-Kasten sitzt die
     Pille in einer Zeilenbox und bekommt deren halbe Durchschuss dazu —
     gemessen stand sie dadurch 2px tiefer als vorher. Auf Blockebene gibt es
     die Zeilenbox nicht, `max-content` hält sie trotzdem schmal. */
  display: block;
  width: max-content;
  max-width: 100%;
  margin: var(--abstand-xs) 0 0;
  padding: 1px var(--abstand-xs);
  border-radius: var(--radius-s);
  background: var(--surface-container-highest);
  color: var(--balken-gruen);
  font-family: var(--schrift-mono);
  font-size: var(--label-small);
  line-height: 1.4;
}
.versandzeile--auf,
.versandzeile--unbekannt { display: none; }

/* In der Tabelle gibt es sie am Desktop gar nicht: Dort trägt die Spalte
   „Gewicht · Preis" ihre Angaben in einer Zeile, und eine vierte Zeile je
   Zelle machte aus der Leseansicht wieder eine Kachelliste. */
.sp-angebot .versandzeile { display: none; }

/* ---------- Die drei Zusatzfassungen der mobilen Liste (18.09.2026) --------

   ⛔ SIE STEHEN IMMER IM MARKUP UND SIND HIER AUSGEBLENDET. Wo die mobile
   Fassung textlich abweicht, trägt das Markup beide Fassungen und das
   Stylesheet entscheidet — dieselbe Bauart wie bei `.werbehinweis-kurz` /
   `-lang` und beim Affiliate-Schalter. In JavaScript zu wählen hieße, die
   Fensterbreite zur Zeichenzeit zu lesen; die Liste wird aber nicht neu
   gezeichnet, wenn jemand das Fenster zieht.

   ⛔ OHNE DIESE DREI ZEILEN STEHEN BEIDE FASSUNGEN NEBENEINANDER, und zwar
   lesbar: Gemessen las die Spaltenüberschrift am Desktop „Preis pro
   KilogrammPreis/kg", und die Unterzeile der Zeile wiederholte Farbe, Shop
   und Menge, die daneben schon je eine eigene Spalte haben. Die Tabelle
   wurde dadurch so breit, daß sie bei 1440px um 75px rollte. */
.tz-unterzeile { display: none; }
.th-kurz { display: none; }
.kachel-gesamt-wort { display: none; }

/* ---------- Brotkrumen (02.09.2026) ---------- */
.brotkrumen { margin-bottom: var(--abstand-l); }

.brotkrumen-liste {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--abstand-xs);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--body-medium);
  color: var(--on-surface-variant);
}

/* Der Trenner entsteht als Pseudoelement: Er ist Zierde und hätte im Markup
   nichts verloren — eine Vorlesehilfe liest sonst zwischen jeder Stufe einen
   Schrägstrich vor. */
.brotkrumen-liste li + li::before {
  content: "/";
  margin-right: var(--abstand-xs);
  color: var(--outline);
}

.brotkrumen-liste a { color: inherit; text-decoration: none; }
.brotkrumen-liste a:hover { text-decoration: underline; }
/* Das letzte Glied ist kein Link und trägt die Textfarbe der Seite: Man ist
   schon dort, und es ist der Name der Sache, um die es geht. */
.brotkrumen-liste [aria-current="page"] { color: var(--on-surface); }

/* Eine Stufe über der Kartenfläche, auf der das Bild liegt. Auf
   --surface-container wäre die Bildfläche von der Karte ringsum nicht zu
   unterscheiden, und der Platzhalter schwebte im Nichts. */
.detail-bildkarte {
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-m);
  overflow: hidden;
  background: var(--surface-container-high);
}
/* Wie auf der Kachel: vollständiges Bild statt mittigem Ausschnitt. Auf der
   Detailseite wiegt das schwerer als in der Liste — hier sieht man sich das
   Produkt an, und ein abgeschnittener Spulenrand wäre genau das, was man
   prüfen will. */
.detail-bildkarte img { width: 100%; height: 100%; object-fit: contain; }
/* ⚠️ .detail-farbchip — die Farbbezeichnung UNTER dem Bild — ist am 07.08.2026
   ersatzlos entfallen. Dasselbe Wort stand als Chip in der Spalte rechts
   daneben, in derselben Blickzeile; die untere Fassung gab keine Auskunft, die
   die obere nicht schon gab. Der Chip rechts bleibt und ist dort als einer der
   Kernwerte zu lesen. Gilt für beide Kategorien. */

/* Textteil der Kopfkarte */
.detail-hersteller { font-size: var(--label-large); color: var(--on-surface-variant); }
.detail-name {
  font-size: var(--headline-large);
  font-weight: 400;
  line-height: var(--zeile-eng);
  margin-top: var(--abstand-xs);
}
.detail-chips { display: flex; flex-wrap: wrap; gap: var(--abstand-s); margin-top: var(--abstand-l); }

/* Kurzfassung des Materials unter den Chips. Bewusst auf derselben Stufe wie
   Hersteller und Chips (14px, gedämpft): Der Satz ordnet ein, er wirbt nicht.
   Auf 16px wäre er der größte Fließtext des Kopfes und liefe dem Produktnamen
   darüber den Rang ab.
   Die Lesebreite begrenzt die Zeile auch dann, wenn die Textspalte des Kopfes
   auf sehr breiten Fenstern auseinandergeht — ein Satz über die volle Breite
   findet am Zeilenende den Anfang der nächsten nicht mehr wieder. */
.detail-kurzfassung {
  margin-top: var(--abstand-l);
  max-width: var(--lesebreite);
  font-size: var(--body-medium);
  line-height: var(--zeile-text);
  color: var(--on-surface-variant);
}
/* Verweis in den Materialartikel, direkt unter der Kurzfassung.

   Unterstrichen, obwohl das Projekt sonst sparsam damit ist: Hier soll er
   nicht nur erreichbar, sondern AUFFINDBAR sein — die Wissensbasis war der
   Teil der Seite, den niemand fand. Farbe allein hätte ihn zwischen den
   gedämpften Sätzen des Kopfes verschwinden lassen.

   Das Buchsymbol ist dasselbe, das die untere Leiste für die Filament-Infos
   trägt: Wer den Verweis einmal benutzt, erkennt das Ziel dort wieder.

   ⚠️ Die Größe des Symbols steht hier ausdrücklich. Die Regel `.knopf svg`
   greift nicht — das ist ein <a>, kein Knopf —, und ein <svg> mit viewBox
   ohne width/height hat keine Eigengröße. Ohne diese Zeilen bekäme es seine
   Größe wieder vom Flex-Algorithmus, mit demselben Ergebnis wie am
   Filterknopf: entweder ein Fleck oder ein Ungetüm. */
.detail-materiallink {
  display: inline-flex;
  align-items: center;
  gap: var(--abstand-s);
  margin-top: var(--abstand-s);
  /* Trefferfläche für den Daumen, ohne die Zeile auseinanderzuziehen. */
  padding: var(--abstand-xs) 0;
  font-size: var(--body-medium);
  font-weight: 500;
  color: var(--primary);
  text-decoration: underline;
}
.detail-materiallink svg {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
}
/* Ohne Kurzfassung folgt der Verweis unmittelbar auf die Datenliste der
   Produktdaten — dann braucht er deren Abstand, nicht den engeren Anschluß
   an einen Satz darüber. */
.detail-datenliste + .detail-materiallink { margin-top: var(--abstand-l); }

.chip-warn {
  display: inline-flex;
  align-items: center;
  gap: var(--abstand-xs);
  padding: var(--abstand-xs) var(--abstand-m);
  border-radius: var(--radius-s);
  border: 1px solid currentColor;
  font-size: var(--label-large);
  font-weight: 500;
}
.chip-warn svg { width: 16px; height: 16px; flex-shrink: 0; }
.chip-warn--erhoeht { color: var(--warnung-erhoeht); }
.chip-warn--hardware { color: var(--error); }

/* Fläche der Fehlerrolle statt Umrandung in der Fehlerrolle — dieselbe
   Bauform wie die Badges und die Währungshinweise. Der Text steht damit auf
   einem geprüften Paar (5,1:1 hell, in beiden Themes aus dem Theme). */
.detail-gefahr {
  margin-top: var(--abstand-l);
  border-radius: var(--radius-s);
  background: var(--error-container);
  color: var(--on-error-container);
  padding: var(--abstand-m);
  font-size: var(--body-medium);
}
/* Farbe trägt hier schon die Fläche; die Hervorhebung braucht keine zweite,
   die im Dunkelmodus gegen den Untergrund arbeiten würde. */
.detail-gefahr strong { color: inherit; font-weight: 500; }

/* --- Anforderungen und Warnhinweise (letzte Karte der Seite) --- */

/* Der Satz aus der Wissensbasis erklärt den Chip darüber; er gehört zu ihm
   und schließt deshalb eng an. Lesebreite wie bei der Kurzfassung im Kopf —
   über die volle Kartenbreite findet man am Zeilenende die nächste Zeile
   nicht mehr wieder. */
.detail-anforderungstext {
  margin-top: var(--abstand-m);
  max-width: var(--lesebreite);
  font-size: var(--body-medium);
  line-height: var(--zeile-text);
  color: var(--on-surface-variant);
}

/* --- Belichtung und Gerät (Resin-Gegenstück zum Temperaturfenster) --- */

/* Links das Harz, rechts das Gerät, dazwischen das Urteil. Auf schmalen
   Fenstern stapeln die drei — nebeneinander blieben bei 390px rund 100px je
   Seite, und „405 nm" bräche mitten im Wert um. */
.harz-abgleich {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--abstand-m);
  margin-bottom: var(--abstand-l);
}

/* Beide Seiten gleich breit und gleich gesetzt: Die Gegenüberstellung soll
   keine der beiden Angaben wichtiger aussehen lassen als die andere. */
.harz-seite {
  flex: 1 1 160px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--abstand-xxs);
  padding: var(--abstand-l) var(--abstand-m);
  border-radius: var(--radius-m);
  background: var(--surface-container-high);
  text-align: center;
}
.harz-seite-label {
  font-size: var(--label-medium);
  color: var(--on-surface-variant);
}
.harz-seite-wert {
  font-size: var(--headline-small);
  font-weight: 500;
  line-height: var(--zeile-eng);
}

/* Ohne gewähltes Gerät steht nur eine Seite da — dann soll sie nicht über die
   ganze Kartenbreite laufen, als sei die zweite bloß vergessen worden. */
.harz-abgleich--allein { justify-content: flex-start; }
.harz-abgleich--allein .harz-seite { flex: 0 1 220px; }

/* Symbol UND Wort, nie nur Farbe — dieselbe Regel wie bei den Badges, und
   dieselben Farbrollen. */
.harz-urteil {
  display: inline-flex;
  align-items: center;
  gap: var(--abstand-xs);
  flex: 0 0 auto;
  padding: var(--abstand-xs) var(--abstand-m);
  border-radius: var(--radius-pille);
  font-size: var(--label-large);
  font-weight: 500;
}
.harz-urteil svg { width: 16px; height: 16px; flex-shrink: 0; }
.harz-urteil--ja { background: var(--gruen-container); color: var(--gruen-inhalt); }
.harz-urteil--nein { background: var(--error-container); color: var(--on-error-container); }
.harz-urteil--offen {
  background: var(--surface-container-high);
  color: var(--on-surface-variant);
}

/* Der Satz sagt dasselbe wie die Gegenüberstellung darüber, nur in Worten —
   für alle, die die Grafik nicht lesen können oder wollen. */
.harz-satz {
  max-width: var(--lesebreite);
  font-size: var(--body-medium);
  line-height: var(--zeile-text);
  color: var(--on-surface-variant);
}

/* Gerätedaten stehen abgesetzt unter dem Urteil: Sie beschreiben das Gerät und
   gehen in die Prüfung darüber ausdrücklich NICHT ein. */
.harz-geraetedaten { margin-top: var(--abstand-xl); }
.harz-fussnote {
  margin-top: var(--abstand-m);
  max-width: var(--lesebreite);
  font-size: var(--label-medium);
  line-height: var(--zeile-text);
  color: var(--on-surface-variant);
}

/* Hauptspalte – Temperaturfenster: oben der schematische Drucker mit den
   konkreten Werten am Bauteil, darunter dieselben Werte als Balken auf der
   gemeinsamen Skala 0–300 °C. Kein Trennstrich dazwischen — beide gehören
   zusammen, getrennt wird hier über den Abstand (siehe Abstandsskala in
   theme.css). */

/* ---------- Der schematische Drucker ist hier ENTFALLEN (03.09.2026) ----------

   .temp-schema, .temp-schema-bild, .temp-schema-linien und .temp-marke* haben
   die Zeichnung samt Führungslinien getragen. Mit Etappe 4 (02.09.2026) ist
   sie aus der Detailseite geflogen — die Werte stehen dort seither als
   Tabelle. Die Regeln standen danach ohne Nutzer da und sind weggeräumt.

   ⚠️ Am 06.09.2026 ist auch die zweite Fassung entfallen: /wissen zeigt
   seither das interaktive Modell des Geräts statt der Schemazeichnung.
   assets/drucker_schema.svg hat damit keinen Leser mehr. Die Bauart
   bleibt trotzdem erwähnenswert: Eine einfarbige Fläche mit der SVG als
   Maske ist der einzige Weg, eine Zeichnung die Textfarbe der Seite
   annehmen zu lassen — über <img> löste currentColor gegen die Bilddatei
   auf, nicht gegen die Seite. */

.temp-skala { display: flex; flex-direction: column; gap: var(--abstand-m); }
.temp-zeile { display: flex; flex-direction: column; gap: var(--abstand-xs); }
.temp-kopf { display: flex; justify-content: space-between; align-items: baseline; gap: var(--abstand-s); }
.temp-label { font-size: var(--label-large); font-weight: 500; }
.temp-bereich { font-size: var(--body-medium); color: var(--on-surface-variant); text-align: right; }
.temp-richtwert { font-style: italic; }
.temp-track {
  position: relative;
  height: 10px;
  border-radius: var(--radius-pille);
  background: var(--surface-container-high);
  /* Dieselbe Begründung wie bei .balken in komponenten.css: Die Schiene ist
     die Skala. Sie stand auf einer Stufe über der Karte und lag damit hell
     bei 1,07:1 — das Temperaturfenster war zu sehen, der Bereich, in dem es
     liegt, nicht. Als inset, damit die 10px Höhe erhalten bleiben.
     Gemessen: 4,69:1 hell, 5,50:1 dunkel gegen die Kartenfläche. */
  box-shadow: inset 0 0 0 1px var(--outline);
}
.temp-fuellung {
  position: absolute;
  top: 0; bottom: 0;
  border-radius: var(--radius-pille);
  background: var(--primary);
}
.temp-fuellung--richtwert { background: var(--outline); }
.temp-achse {
  display: flex;
  justify-content: space-between;
  margin-top: var(--abstand-s);
  font-size: var(--label-small);
  color: var(--on-surface-variant);
}

/* Spalte 2 – Produktdaten */
.detail-datenliste { margin: 0; }
/* Datenzeilen ohne Trennlinien: Jede Zeile ist einzeilig, Beschriftung links
   und Wert rechts stehen jeweils auf einer gemeinsamen Kante. Der größere
   Zeilenabstand trennt zuverlässiger als 1px, die zwölf Linien der Karte
   entfallen. */
.detail-datenzeile {
  display: flex;
  justify-content: space-between;
  gap: var(--abstand-m);
  padding: var(--abstand-s) 0;
  font-size: var(--body-medium);
}
.detail-datenzeile dt { color: var(--on-surface-variant); }
.detail-datenzeile dd { margin: 0; text-align: right; }

/* Der Preisblock im Kopf (24.09.2026, Bildvorlage) — die Klassen heißen
   weiter `kaufbox-*`, es ist dieselbe Aussage wie in der früheren Kaufbox der
   Seitenspalte: der „ab"-Preis des Produkts. Schrift und Größe des Preises
   sind unverändert. */
.kaufbox-preis {
  font-size: var(--display-medium);
  font-weight: 700;
  color: var(--primary);
  line-height: var(--zeile-eng);
  white-space: nowrap;
}
/* Das „ab" als eigene kleine Zeile über dem Betrag (Vorlage). Es ist der
   Baustein .kicker — Größe, Familie und Versalien kommen von dort. */
.kaufbox-ab { display: block; margin-bottom: var(--abstand-xxs); }
.kaufbox-betrag { display: block; }
.kaufbox-preis-einheit { font-size: var(--body-medium); color: var(--on-surface-variant); }
/* „pro Kilogramm inkl. Versand" steht in EINER Zeile; der Baustein ist
   sonst eine eigene Zeile unter dem Preis. */
.kaufbox-preis-einheit .versand-zusatz { display: inline; margin-top: 0; }
/* Unter 768px nebeneinander unter dem Preis, ab 768px übereinander rechts
   daneben (Vorlage). */
.kaufbox-nebenaktionen {
  display: flex;
  flex-wrap: wrap;
  gap: var(--abstand-s);
  flex: 0 0 auto;
}
@media (min-width: 768px) {
  .kaufbox-nebenaktionen { flex-direction: column; flex-wrap: nowrap; }
}

/* Spalte 3 – Weitere Shops */
.weitere-liste { display: flex; flex-direction: column; }
.weitere-zeile {
  display: flex;
  justify-content: space-between;
  gap: var(--abstand-m);
  padding: var(--abstand-s) 0;
  font-size: var(--body-medium);
}
.weitere-shop { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.weitere-preis { font-weight: 700; color: var(--primary); white-space: nowrap; }

/* Nicht lieferbares Angebot. Es bleibt vollständig lesbar — der Preis stimmt
   ja, und oft ist es gerade der günstigste. Zurückgenommen wird nur die
   Auszeichnung: Der Preis verliert die Markenfarbe und wird zur normalen
   Textfarbe, der Shopname rückt eine Stufe leiser.

   Bewusst KEIN opacity: Das dämpft Preis und Hinweis gleichermaßen, also auch
   die Zeile, die den Grund nennt — und drückt den Kontrast unter AA, ohne
   eine einzige zusätzliche Aussage zu treffen. */
/* Auch der Shopname selbst, der als Link sonst die Markenfarbe trägt. Die
   Unterstreichung bleibt: Der Link funktioniert weiterhin, er drängt sich nur
   nicht mehr auf. Nur die Farbe zurückzunehmen und den Unterstrich zu
   behalten, hält die Bedienbarkeit sichtbar. */
.weitere-zeile--aus .weitere-shop,
.weitere-zeile--aus .weitere-shop a { color: var(--on-surface-variant); }
.weitere-zeile--aus .weitere-preis { color: var(--on-surface); font-weight: 500; }

/* Der Hinweis selbst ist .nicht-lieferbar-hinweis (komponenten.css) — er
   steht wortgleich auch an der Kachel und gehört deshalb zu den gemeinsamen
   Bausteinen, nicht hierher. */

/* ---------- Preisverlauf (Hauptspalte, direkt unter dem Kopf) ----------

   Eine Datenreihe, deshalb EINE Farbe (--primary) und keine Legende: Der
   Kartentitel benennt bereits, was die Linie zeigt. Die Achsen sind
   durchgezogene Linien in --outline, die drei mittleren Gitterlinien seit dem
   24.09.2026 gestrichelt (Bildvorlage) — als Hilfslinien der Skala, nicht als
   Schwelle; die Kurve selbst ist die einzige kräftige Linie.

   Die Beschriftungen stehen als HTML NEBEN und UNTER dem Bild, nicht darin.
   Text im SVG würde mit der Grafik skalieren und wäre auf dem Handy halb so
   groß wie der Text daneben. */

/* Die Karte hebt sich von den übrigen ab: eine Flächenstufe höher, dazu eine
   Kante. Beides zusammen und beides zurückhaltend — der Preisverlauf ist das
   Kernstück der Seite, aber der preisProKg in der Kaufbox bleibt mit 44px
   Fettdruck in --primary das stärkste Einzelelement. Der Verlauf ergänzt ihn
   über seine FLÄCHE (doppelte Diagrammhöhe, volle Spaltenbreite), nicht über
   eine größere oder buntere Schrift.

   ⚠️ --verlauf-flaeche ist nicht bloß eine Abkürzung: Die Punktringe im
   Diagramm sind in der Kartenfläche gestrichelt, damit sie sich als Ringe von
   der Linie absetzen (siehe .verlauf-marke). Stünde die Farbe dort fest auf
   --surface-container, säßen nach dieser Änderung Ringe in der Farbe der
   NACHBARKARTEN auf einer helleren Fläche. */
.detail-verlauf {
  --verlauf-flaeche: var(--surface-container-high);
  background: var(--verlauf-flaeche);
  border: 1px solid var(--outline-variant);
}
/* Untertitel gehört zur Überschrift und rückt deshalb dicht darunter. Seit
   der Verlauf klappt (24.09.2026), steht er im Klappinhalt — der Abstand
   kommt deshalb aus dessen Polster, nicht aus der Überschrift (die gibt ihren
   Abstand an .detail-klapp ab). */
.detail-verlauf .klapp-inhalt { padding-top: var(--abstand-xs); }
.verlauf-quelle {
  font-size: var(--body-small);
  color: var(--on-surface-variant);
  margin-bottom: var(--abstand-l);
}

/* Das LEERE Diagramm (24.09.2026): Raster und Zeitachse wie beim gefüllten,
   die Fläche ausgegraut, der Satz in der Mitte. Ausgegraut über die
   abgesetzte Fläche (--surface-container-highest) und nicht über opacity —
   eine Deckkraft träfe auch den Satz, und der muss 4,5:1 halten
   (--on-surface-variant auf -highest steht in pruefeTextAufFlaeche). */
.verlauf-leerflaeche { fill: var(--surface-container-highest); }
/* Titel und Satz als Block in der Mitte der Fläche (Bildvorlage): der
   Titel in Schreibmaschinenschrift, fett, in voller Textfarbe, der Satz
   darunter in der Grotesk und gedämpft. */
.verlauf-leer-text {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--abstand-s);
  padding: var(--abstand-l);
  text-align: center;
}
.verlauf-leer-titel {
  margin: 0;
  font-family: var(--schrift-mono);
  font-size: var(--title-large);
  font-weight: 700;
  color: var(--on-surface);
}
.verlauf-leer-satz {
  margin: 0;
  font-size: var(--body-medium);
  color: var(--on-surface-variant);
}

/* Zeitraumleiste über dem Diagramm. Eng zusammengeschoben statt mit dem
   Chip-Abstand gesetzt: Die dichte Reihe liest sich als EINE Einstellung mit
   vier Stufen, vier freistehende Chips sähen wie vier unabhängige Filter aus.
   Bewusst nicht .chip — der setzt bei aria-pressed ein Häkchen davor, das hier
   nur sagte „ausgewählt", wo ohnehin immer genau einer ausgewählt ist. */
.verlauf-zeitraum {
  display: flex;
  flex-wrap: wrap;
  gap: var(--abstand-xxs);
  margin-bottom: var(--abstand-m);
}
.verlauf-zeitraum-knopf {
  min-height: 32px;
  padding: 0 var(--abstand-m);
  border-radius: var(--radius-pille);
  border: 1px solid var(--outline);
  background: none;
  color: var(--on-surface-variant);
  font-size: var(--label-large);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--dauer-kurz) ease, border-color var(--dauer-kurz) ease;
}
/* --highest, nicht --high: Die Verlaufskarte liegt selbst auf --high, ein
   Überfahren auf derselben Stufe wäre unsichtbar. */
.verlauf-zeitraum-knopf:hover:not([aria-disabled="true"]) {
  background: var(--zustand-hover);
}
.verlauf-zeitraum-knopf[aria-pressed="true"] {
  background: var(--secondary-container);
  color: var(--on-secondary-container);
  border-color: transparent;
  font-weight: 600;
}
/* Gesperrt heißt hier „dafür reichen die Daten nicht zurück", nicht „kaputt":
   blass, aber noch lesbar — der Zeitraum soll als vorhandene, nur gerade nicht
   belegbare Möglichkeit erkennbar bleiben. Die Sperre steht auf aria-disabled
   und NICHT auf dem disabled-Attribut (siehe js/preisverlauf.js): Der Knopf
   bleibt anfassbar, damit sein title die Begründung überhaupt zeigen kann. */
/* ⚠️ 0,72 statt 0,45 (10.08.2026): Der Kommentar darüber formuliert die
   Absicht wörtlich — „blass, aber noch lesbar". Mit 0,45 war sie nicht
   eingelöst: gemessen 2,35:1, im zugleich gewählten Zustand sogar 2,21:1
   (--on-secondary-container auf --secondary-container hat weniger Reserve als
   Dunkelgrau auf Weiß). Beide unter 4,5:1. Die Sperre trägt mit
   cursor: not-allowed und dem title ohnehin zwei nicht-farbliche Merkmale. */
.verlauf-zeitraum-knopf[aria-disabled="true"] {
  opacity: 0.72;
  cursor: not-allowed;
}

/* Achsenspalte links: ⚠️ muss zu ACHSENSPALTE in js/preisverlauf.js passen.
   Auf dem Diagramm-Container, damit Zeichenfläche UND Zeitachse darunter
   denselben Wert erben und bündig stehen. */
.verlauf-diagramm { --verlauf-achsenspalte: 88px; }
.verlauf-plot {
  position: relative;
  padding-left: var(--verlauf-achsenspalte);
}
/* Bezugsrahmen NUR für die Zeichenfläche: Höhe und Breite sind hier
   identisch mit denen der Grafik, weshalb die Kurzanzeige darin in Prozent
   gesetzt werden kann (siehe js/preisverlauf.js). */
.verlauf-bild { position: relative; }
.verlauf-svg { display: block; width: 100%; height: auto; overflow: visible; }

/* Die Marken sitzen prozentual auf der Höhe ihres Werts. Prozent statt Pixel,
   weil die Grafik mit der Spaltenbreite skaliert — eine Pixelangabe wanderte
   beim Verkleinern des Fensters von ihrer Linie weg. */
.verlauf-ymarke {
  position: absolute;
  left: 0;
  width: calc(var(--verlauf-achsenspalte) - var(--abstand-s));
  transform: translateY(-50%);
  text-align: right;
  /* Abgelesene Skalenwerte → Schreibmaschinenschrift (Bildvorlage). */
  font-family: var(--schrift-mono);
  font-size: var(--label-medium);
  font-variant-numeric: tabular-nums;
  color: var(--on-surface-variant);
  white-space: nowrap;
}

/* Zeitachse: die Tagesmarken sitzen unter ihren Strichen, in Prozent der
   Bildbreite — dieselbe Rechnung wie die Striche im SVG. Die Zeile braucht
   deshalb eine eigene Höhe (ihre Kinder liegen absolut). Der Abstand oben
   lässt Platz für die 6px-Striche unter der Achslinie. */
.verlauf-xachse {
  position: relative;
  height: calc(var(--label-medium) * 1.5);
  margin: calc(6px + var(--abstand-xs)) 0 0 var(--verlauf-achsenspalte);
  font-size: var(--label-medium);
  font-variant-numeric: tabular-nums;
  color: var(--on-surface-variant);
}
.verlauf-xmarke {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  white-space: nowrap;
}
/* Am Rand rückt die Marke nach innen, sonst schnitte der Klappinhalt sie ab. */
.verlauf-xmarke--anfang { transform: none; }
.verlauf-xmarke--ende { transform: translateX(-100%); }

/* Achsentitel (24.09.2026, Bildvorlage): „Preis in €/kg" über der Skala,
   senkrecht „PREIS" am linken Rand, „DATUM" mittig unter der Zeitachse. Die
   Versalien entstehen über text-transform, im Markup steht gemischter Text
   (Vorlesehilfen buchstabieren durchgehende Versalien sonst). */
/* Abstand nach unten ≥ halbe Zeilenhöhe der Skala: Die oberste Marke sitzt
   mittig auf der obersten Linie und ragt um ihre halbe Höhe nach oben. */
.verlauf-ytitel {
  margin: 0 0 var(--abstand-m);
  font-size: var(--label-medium);
  color: var(--on-surface-variant);
}
.verlauf-yachsentitel,
.verlauf-xtitel {
  font-size: var(--label-medium);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--on-surface-variant);
}
.verlauf-yachsentitel {
  position: absolute;
  left: 0;
  top: 50%;
  writing-mode: vertical-rl;
  transform: translateY(-50%) rotate(180deg);
  white-space: nowrap;
}
.verlauf-xtitel {
  margin: var(--abstand-xs) 0 0 var(--verlauf-achsenspalte);
  text-align: center;
}

.verlauf-gitter line { stroke: var(--outline-variant); stroke-width: 1; }
/* Die drei mittleren Linien gestrichelt (Bildvorlage), die oberste und die
   Achse durchgezogen: Gestrichelt heißt hier „Hilfslinie der Skala". */
.verlauf-gitter .verlauf-gitter--strich { stroke-dasharray: 6 6; }
/* Die Achslinien sind die Grenze der Zeichenfläche und tragen deshalb die
   3:1 grafischer Objekte (--outline), anders als das leise Gitter. */
.verlauf-achsen line { stroke: var(--outline); stroke-width: 1; }
.verlauf-tonung { fill: var(--primary); fill-opacity: 0.1; stroke: none; }
.verlauf-linie {
  fill: none;
  stroke: var(--primary);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}
/* Strichstärken bleiben in Bildschirmpixeln, auch wenn das SVG kleiner
   gerechnet als dargestellt wird (oder umgekehrt). */
.verlauf-gitter line,
.verlauf-achsen line,
.verlauf-linie,
.verlauf-faden,
.verlauf-marke { vector-effect: non-scaling-stroke; }

/* ⚠️ 0,75 statt der früheren 0,55 (11.08.2026). Die Punkte sind die
   gemessenen Preisstände und damit Inhalt, nicht Beiwerk — sie fallen unter
   die 3:1 für grafische Objekte und lagen mit 0,55 bei 2,54:1 hell und
   2,52:1 dunkel. Ein Wert für beide Schemata: 3,70 hell, 3,51 dunkel.
   Dass sie leiser sind als die beiden Marken, bleibt erhalten — die tragen
   volle Deckkraft UND einen Ring in Kartenfarbe, also zwei Unterschiede. */
.verlauf-punkt { fill: var(--primary); fill-opacity: 0.75; }
/* Ring in Kartenfarbe statt Kontur: derselbe Trenner wie zwischen zwei
   Flächen, nur rund. --verlauf-flaeche kommt von der Karte (siehe dort); der
   Rückfallwert greift, falls das Diagramm je woanders eingesetzt wird. */
.verlauf-marke {
  fill: var(--primary);
  stroke: var(--verlauf-flaeche, var(--surface-container));
  stroke-width: 2;
}
/* Der günstigste Stand als offener Ring, der letzte als voller Punkt: zwei
   unterscheidbare Formen in EINER Farbe. Eine zweite Farbe läse sich als
   zweite Datenreihe, und 1 px Größenunterschied wäre kein Unterschied. */
.verlauf-marke--tief {
  fill: var(--verlauf-flaeche, var(--surface-container));
  stroke: var(--primary);
}

.verlauf-faden { stroke: var(--outline); stroke-width: 1; }
.verlauf-zeiger {
  fill: var(--primary);
  stroke: var(--verlauf-flaeche, var(--surface-container));
  stroke-width: 2;
}
/* hidden greift bei SVG-Kindern nicht über das Standard-Stylesheet des
   Browsers — hier ausdrücklich. */
.verlauf-faden[hidden], .verlauf-zeiger[hidden] { display: none; }
/* Ganze Fläche als Ziel: Der nächstgelegene Punkt wird gesucht, nicht
   getroffen. */
.verlauf-fangflaeche { fill: transparent; cursor: crosshair; }

.verlauf-tooltip {
  position: absolute;
  transform: translate(-50%, calc(-100% - 12px));
  padding: var(--abstand-xs) var(--abstand-s);
  border-radius: var(--radius-s);
  background: var(--inverse-surface);
  color: var(--inverse-on-surface);
  box-shadow: var(--schatten-schwebend);
  font-size: var(--body-small);
  line-height: var(--zeile-normal);
  white-space: nowrap;
  pointer-events: none;
  z-index: 1;
}
.verlauf-tooltip--unten { transform: translate(-50%, 12px); }
.verlauf-tooltip strong { display: block; font-weight: 700; }
.verlauf-tooltip span { font-variant-numeric: tabular-nums; opacity: 0.85; }

/* Die Kernaussage unter dem Diagramm: Fließtextgröße statt Nebentext und die
   volle Textfarbe. Sie fasst zusammen, was die Kurve zeigt, und ist damit
   keine Fußnote — auf 14px in --on-surface-variant las sie sich wie eine. */
.verlauf-tief {
  margin-top: var(--abstand-l);
  font-size: var(--body-large);
  line-height: var(--zeile-normal);
  color: var(--on-surface);
}
.verlauf-tief strong { color: var(--primary); font-weight: 700; }

/* ---------- Merkzettel ---------- */

.merkzettel-liste { margin-top: var(--abstand-2xl); }

/* Zwischenüberschrift der Kategoriegruppen im Ordner. Sie erscheint nur, wenn
   beide Kategorien im Ordner liegen (js/seite-merkzettel-ordner.js) — deshalb
   trennt sie hier immer zwei Blöcke und darf entsprechend Luft bekommen.
   Erste Gruppe ohne oberen Abstand: Darüber steht bereits der Seitentitel. */
.merk-gruppe {
  font-size: var(--headline-small);
  font-weight: 500;
  line-height: var(--zeile-eng);
  margin-bottom: var(--abstand-l);
}
.merk-gruppe + * { margin-top: 0; }
.kachel-liste + .merk-gruppe { margin-top: var(--abstand-3xl); }

/* ---------- Merkzettel: Ordner ---------- */

.merkzettel-kopfzeile {
  display: flex;
  justify-content: flex-start;
  margin-top: var(--abstand-l);
}

/* Handy einspaltig, ab 320px Kartenbreite mehrspaltig — dasselbe Raster wie
   die Materialflächen der Startseite. */
.ordner-raster {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--abstand-l);
}

/* Wie die Materialkachel: Die Fläche führt in den Ordner, die Knöpfe darin
   tun etwas anderes und liegen deshalb über der Klickfläche. */
.ordner-karte {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--abstand-xs);
  /* ⚠️ Ein Rasterkind bekommt von sich aus `min-width: auto` und wird damit
     nie schmaler als sein längstes unteilbares Wort. Eine 300 Zeichen lange
     Folge ohne Leerzeichen im Ordnernamen blähte die Kachel deshalb über die
     Spur hinaus. Die Textelemente darin brechen zwar jetzt (siehe unten),
     aber diese Zeile ist die Absicherung darunter: Sie gilt auch für alles,
     was hier künftig noch hineinkommt. */
  min-width: 0;
  padding: var(--abstand-xl);
  border-radius: var(--radius-m);
  /* ⚠️ background-COLOR, nicht die Kurzform: Die Zustandsebene liegt als
     background-image darüber (siehe „Zustandsebene auf eigener Fläche" in
     komponenten.css), und die Kurzform setzte sie bei jedem Zeichnen wieder
     auf `none` zurück. seiten.css wird NACH komponenten.css geladen, die
     Rücknahme gewänne also. */
  background-color: var(--surface-container);
  transition: background-color var(--dauer-kurz) ease,
              background-image var(--dauer-kurz) ease,
              transform var(--dauer-kurz) ease;
}
.ordner-karte:hover { transform: translateY(-2px); }
.ordner-karte:active { transform: none; }

.ok-hauptlink { text-decoration: none; }
.ok-hauptlink::after { content: ""; position: absolute; inset: 0; }

/* ---------- Freitext des Nutzers in der Kachel ----------

   Name und Notiz kommen aus einem Eingabefeld und dürfen deshalb alles
   enthalten — auch 300 Zeichen ohne ein einziges Leerzeichen. Ohne die
   folgenden Regeln lief so eine Folge aus der Kachel heraus, über die
   ganze Seitenbreite hinaus, und das Dokument bekam einen waagerechten
   Rollbalken.

   ⚠️ `anywhere` und nicht `break-word`, und das ist der Punkt: Nur
   `anywhere` geht auch in die MINDESTBREITE des Elements ein. Mit
   `break-word` bräche der Text zwar um, das Element behielte aber die
   Mindestbreite seines längsten Wortes — und damit bliebe die Kachel zu
   breit. Der Unterschied ist unsichtbar, solange nichts überläuft, und
   genau dann sichtbar, wenn es darauf ankommt.

   Die Zeilenbegrenzung ist die zweite Hälfte: Ein Name, der vor der
   Längengrenze angelegt wurde, und eine Notiz von 200 Zeichen füllten die
   Kachel sonst mit einer Textwand. `-webkit-line-clamp` setzt das
   Auslassungszeichen von selbst; den vollständigen Text trägt das
   title-Attribut (js/seite-merkzettel.js). */
.ok-name {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  /* Zwei Zeilen: Ein erlaubter Name (40 Zeichen) passt darin auf jeder
     Kachelbreite vollständig; abgeschnitten wird damit nur, was von vor der
     Begrenzung stammt. */
  -webkit-line-clamp: 2;
  overflow: hidden;
  overflow-wrap: anywhere;
  font-size: var(--title-large);
  font-weight: 500;
  color: var(--primary);
}
.ok-anzahl { font-size: var(--body-medium); color: var(--on-surface-variant); }
.ok-notiz {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
  overflow-wrap: anywhere;
  /* ⚠️ Über die Klickfläche gehoben — sonst gäbe es den title-Hinweis
     nicht. `.ok-hauptlink::after` liegt mit `inset: 0` über der ganzen
     Kachel; alles darunter bekommt kein Hover ab, und ein title an einem
     verdeckten Element erscheint nie. Dieselbe Technik wie bei
     `.ok-aktionen` daneben. Der Preis ist bewusst gewählt: Ein Klick auf
     die Notiz öffnet den Ordner nicht mehr — Name, Anzahl und die freie
     Fläche der Kachel tun es weiterhin. */
  position: relative;
  z-index: 1;
  font-size: var(--body-medium);
  color: var(--on-surface-variant);
  margin-top: var(--abstand-xs);
}

/* Die Restzeichenanzeige der Ordnerfelder: dezent, rechtsbündig unter dem
   Feld, und nur da, wenn es knapp wird (js/ordner.js, RESTHINWEIS_AB). */
.feld-rest {
  align-self: flex-end;
  font-size: var(--label-medium);
  color: var(--on-surface-variant);
}
.feld-rest[hidden] { display: none; }

.ok-aktionen {
  position: relative;
  z-index: 1;
  display: flex;
  gap: var(--abstand-s);
  margin-top: auto;
  padding-top: var(--abstand-m);
}

/* ⚠️ `margin-left: auto` schiebt sie an das rechte Ende der Zurück-Zeile.
   Bis zum 12.09.2026 tat das ein `justify-content: space-between` an der
   entfallenen `.detail-zurueckzeile` — die gemeinsame `.zurueckzeile` kann das
   nicht übernehmen: Dort steht rechts vom Pfeil in der Regel die Brotkrumen-
   reihe, und die soll DICHT an ihm stehen, nicht am anderen Seitenrand. */
.ordner-kopf-aktionen { display: flex; gap: var(--abstand-s); margin-left: auto; }

/* Verschieben-Knopf der Kachel: eigene Zeile über dem Shop-Knopf, damit er
   nicht mit ihm um die Aufmerksamkeit ringt. */
.kachel-verschieben {
  position: relative;
  z-index: 1;
}

/* --- Formular und Wege in den Sheets --- */

.ordner-formular {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-l);
}

.ordner-feld {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-xs);
  font-size: var(--label-large);
  font-weight: 500;
}
.ordner-feld small { font-weight: 400; color: var(--on-surface-variant); }

.ordner-feld input,
.ordner-feld textarea {
  width: 100%;
  padding: var(--abstand-s) var(--abstand-m);
  border: 1px solid var(--outline);
  border-radius: var(--radius-s);
  background: var(--surface-container-lowest);
  color: var(--on-surface);
  font-size: var(--body-large);
  font-weight: 400;
  font-family: inherit;
  resize: vertical;
}

.ordner-wege {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-s);
}
/* Der endgültige Weg trägt die Fehlerrolle — er ist nicht rückgängig zu
   machen und soll sich vom anderen unterscheiden, nicht nur im Text. */
.ordner-weg--loeschen { color: var(--error); border-color: var(--error); }

.ordner-wahl {
  display: flex;
  flex-direction: column;
}

/* ---------- Merkzettel: Mehrfachauswahl (18.09.2026) ----------

   ⛔ DIE LEISTE STEHT AM KOPF UND KLEBT — sie ist KEINE schwebende Leiste wie
   die Vergleichsauswahl. Auf denselben beiden Routen ist jene bereits
   zugelassen, und über ihr staffelt sich die Snackbar; eine dritte feste
   Schicht am unteren Rand wäre genau die Ecke, die am 18.09.2026 erst
   geradegerückt worden ist. Die ausführliche Begründung steht im Kopf von
   js/merkauswahl.js. */

.mz-auswahlzeile {
  display: flex;
  justify-content: flex-start;
  margin-bottom: var(--abstand-l);
}

.mz-auswahlleiste {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--abstand-s) var(--abstand-m);

  /* Klebt wie die Werkzeugzeile der Listen, mit DEMSELBEN Maß: Zwei
     verschiedene Werte ließen die beiden Kanten um wenige Pixel gegeneinander
     versetzt stehen, und das sieht nach einem Fehler aus, obwohl beide für
     sich richtig gerechnet sind.

     ⛔ .mz-auswahlleiste STEHT DAFÜR IN DER EMPFÄNGERLISTE der gemessenen
     Kopfhöhe (css/layout.css). Ohne den Eintrag bekäme sie den Startwert 56px
     und rutschte unter die Kopfleiste — genau der Fehler, den --kopf-hoehe
     einmal beheben sollte.

     ⚠️ Sie steht INNERHALB des Bereichs, auf den sie sich bezieht (Geräte-
     Abschnitt bzw. Ordnerliste). `position: sticky` endet an dessen
     Unterkante von selbst; darunter, wo nichts mehr wählbar ist, schwebt
     also nichts mehr.

     z-index 30 wie die Werkzeugzeile: über den Kacheln, unter Vergleichsleiste
     (40), Hauptleiste (45), Kopfleiste (50) und allem Modalen (60+). */
  position: sticky;
  top: calc(var(--kopf-hoehe) + var(--abstand-l));
  z-index: 30;
  background: var(--surface-container);
  box-shadow: var(--karten-kante);
  border-radius: var(--radius-m);
  padding: var(--abstand-s) var(--abstand-m);
  margin-bottom: var(--abstand-l);
}

/* Der Streifen über der Leiste — sonst laufen die Kacheln beim Rollen
   SICHTBAR durch die Lücke, die sie von der Kopfleiste abhält. Dieselbe
   Bauart und derselbe Grund wie bei .listen-kopf::before. */
.mz-auswahlleiste::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: var(--abstand-l);
  background: var(--background);
}

.mz-auswahl-zahl {
  margin: 0;
  /* Eine abgelesene Zahl — dieselbe Regel wie überall: Was gemessen oder
     gezählt ist, steht in Schreibmaschinenschrift. */
  font-family: var(--schrift-mono);
  font-size: var(--label-medium);
  color: var(--on-surface-variant);
}

.mz-auswahl-wege,
.mz-auswahl-aktionen {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--abstand-xs);
}
/* Die Aktionen rechts, die beiden Auswahlwege links daneben. */
.mz-auswahl-aktionen { margin-left: auto; }

/* ⚠️ 44px statt der 40px der Grundregel — so beauftragt, und weit über den
   24px aus WCAG 2.5.8. Gesetzt wird es hier und nicht an `.knopf`: Dieselbe
   Klasse trägt jeden Knopf der Seite, und eine globale Erhöhung verschöbe
   Geometrie an Stellen, die niemand angeschaut hat. */
.mz-auswahlleiste .knopf { min-height: 44px; }

/* Der endgültige Weg trägt die Fehlerrolle — wie bei den Ordner-Wegen: Er ist
   nicht rückgängig zu machen und soll sich nicht nur im Text unterscheiden. */
.mz-auswahl-loeschen { color: var(--error); border-color: var(--error); }
/* Im Bestätigungsblatt ist er der gefüllte Hauptknopf; dort braucht die
   Fläche die Fehlerrolle, nicht die Schrift. */
.sheet-fuss .knopf.mz-auswahl-loeschen {
  background: var(--error-container);
  color: var(--on-error-container);
  border-color: transparent;
}

/* ---------- Das Kontrollkästchen ---------- */

/* ⚠️ DIE TIPPFLÄCHE IST DIE GANZE KACHEL, nicht das Kästchen. Beide sind in
   ein <label> gewickelt, und `.kachel-hauptbereich::after { inset: 0 }` deckt
   die Karte ohnehin ab — ein Tipp irgendwo darauf schaltet das Kästchen.
   Die 24px hier sind das SICHTBARE Maß; sie liegen über den 24px aus
   WCAG 2.5.8, falls jemand doch genau zielt. */
.auswahl-haken {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  margin: 0 var(--abstand-s) 0 0;
  accent-color: var(--secondary);
  cursor: pointer;
}

/* Der Hauptbereich ist im Auswahlmodus ein <label> statt eines <a>. Am Layout
   ändert das nichts — die Klasse bleibt dieselbe; hier steht nur, was ein
   Label nicht von sich aus mitbringt. */
.kachel-hauptbereich--waehlen { cursor: pointer; }

/* ⚠️ Ohne eigenen Rand in der Kachel: Das Kästchen steht seit dem 22.09.2026
   in `.kachel-kopf`, und der Abstand zum Bild kommt aus deren Lücke. Der
   Rand der Grundregel gilt weiter für die Gerätezeile, die keine Lücke hat. */
.kachel-kopf .auswahl-haken { margin: 0; }

/* ⛔ DIE AUSWAHL NIMMT --secondary, DER VERGLEICH --primary. Die beiden
   Auswahlen sind ausdrücklich getrennt (der Auswahlmodus rührt die
   Vergleichsauswahl nicht an) — trügen sie dieselbe Farbe, läsen sie sich als
   dieselbe Sache. Eine Kachel kann beides zugleich sein; dann gewinnt die
   spätere Regel, und das ist die Auswahl: Sie ist der Modus, in dem man
   gerade steht. */
/* Die Kachel trägt ohnehin `border: 2px solid transparent` (komponenten.css) —
   sie einzufärben kostet kein Layout. Gemessen: 8,81:1 gegen die Kachelfläche
   und 10,46:1 gegen die Seite, also weit über den 3:1, die WCAG 1.4.11 für
   ein grafisches Objekt fordert. */
.filament-kachel.mz-gewaehlt {
  border-color: var(--secondary);
  background: var(--zustand-hover);
}

/* ⛔ DIE GERÄTEZEILE BEKOMMT EINE KONTUR, KEINE KANTE — und das ist gemessen,
   nicht Geschmack.
   Sie hat von Haus aus keinen Rahmen. Ein `border` im Auswahlmodus legt 2px
   auf die Zeilenhöhe: gemessen 70,4 → 72,4px, also ein sichtbarer Sprung
   ALLER Zeilen beim Umschalten. Der naheliegende Ausgleich (Polsterung um
   1px zurücknehmen) griff nicht — `.mz-drucker-zeile--waehlbar` und die
   Grundregel `.mz-drucker-zeile` wiegen gleich schwer (0,1,0), und die
   Grundregel steht rund 3000 Zeilen WEITER UNTEN in dieser Datei. Es
   entscheidet allein die Reihenfolge; dieselbe Falle wie bei den Kategorien
   der Kopfleiste und den Container-Stufen der Tabelle.
   `outline` belegt kein Layout und kann deshalb gar nicht springen — der
   Spezifitätskampf entfällt mitsamt seiner Ursache. `outline-offset: -1px`
   zeichnet die Linie nach innen, damit sie der Rundung folgt. */
.mz-drucker-zeile.mz-gewaehlt {
  outline: 1px solid var(--secondary);
  outline-offset: -1px;
  background: var(--zustand-hover);
}
/* ⚠️ EIN RASTER, KEINE FLEX-ZEILE. Das Label führt drei Kinder — Kästchen,
   Name, Datenzeile —, und Name und Daten sollen wie zuvor im Link
   untereinander stehen. Im Flex mit Umbruch landete das Kästchen dadurch
   allein in der ersten Zeile und der Name erst darunter. Im Raster steht es
   in Spalte 1 über beide Zeilen, rechts daneben Name über Daten. */
.mz-drucker-waehlen {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  flex: 1;
  min-width: 0;
  cursor: pointer;
}
.mz-drucker-waehlen .auswahl-haken { grid-row: 1 / span 2; }

/* ---------- Produktvergleich (Bildvorlage, 04.09.2026) ---------- */

/* Kopfzeile: Titel mit Anzahl links, Warnpille und Schalter rechts.
   ⚠️ `flex-wrap` plus `margin-left: auto` am Schalter: Auf schmalen Fenstern
   bricht die Zeile um, statt Titel und Schalter aneinanderzudrücken. */
.pv-kopf {
  display: flex;
  align-items: center;
  gap: var(--abstand-m);
  flex-wrap: wrap;
  margin-bottom: var(--abstand-l);
  padding-bottom: var(--abstand-m);
  border-bottom: 1px solid var(--outline-variant);
}
.pv-titelzeile {
  display: flex;
  align-items: baseline;
  gap: var(--abstand-s);
  min-width: 0;
}
.pv-titelzeile h1 { margin: 0; }
/* Eine ABGELESENE Zahl — Monospace, wie jede technische Marke der Seite. */
.pv-anzahl {
  flex: 0 0 auto;
  font-family: var(--schrift-mono);
  font-size: var(--label-large);
  color: var(--on-surface-variant);
}
/* Die Warnpille trägt beide Sätze: gemischte Materialien UND „kein
   Qualitätsurteil". ⚠️ `--warnung-erhoeht` ist auf der Kartenfläche als Text
   geprüft; die Pille steht auf `--surface-container-high`. */
.pv-warnung {
  display: flex;
  align-items: center;
  gap: var(--abstand-s);
  margin: 0;
  padding: var(--abstand-xs) var(--abstand-m);
  border: 1px solid var(--warnung-erhoeht);
  border-radius: var(--radius-pille);
  background: var(--surface-container-high);
  color: var(--warnung-erhoeht);
  font-size: var(--label-large);
}
.pv-warnung svg { width: 16px; height: 16px; flex: 0 0 auto; }
.pv-schalter {
  margin-left: auto;
  flex: 0 0 auto;
  font-size: var(--label-large);
}

/* Band für die zwei Lagen, die eine Spalte nicht ausdrücken kann:
   kein Wechselkurs und Preisgleichstand. */
.pv-band {
  display: flex;
  align-items: center;
  gap: var(--abstand-s);
  margin-bottom: var(--abstand-l);
  padding: var(--abstand-m);
  border: 1px solid var(--outline-variant);
  border-radius: var(--radius-m);
  background: var(--surface-container);
  font-size: var(--body-medium);
  color: var(--on-surface);
}
.pv-band svg { width: 18px; height: 18px; flex: 0 0 auto; color: var(--primary); }
/* Ohne ermittelbaren Sieger ist es eine Auskunft über eine Lücke, kein
   Erfolg — deshalb die neutrale Umrandung und kein Sternton. */
.pv-band--offen svg { color: var(--on-surface-variant); }

/* Der Hinweis, wenn „Nur Unterschiede" alles ausblendet. */
.pv-kein-unterschied {
  margin: var(--abstand-xl) 0 0;
  padding: var(--abstand-xl);
  border: 1px dashed var(--outline);
  border-radius: var(--radius-m);
  text-align: center;
  font-size: var(--body-medium);
  color: var(--on-surface-variant);
}

.vergleich-aktionen {
  margin-top: var(--abstand-xl);
  padding-top: var(--abstand-l);
  border-top: 1px solid var(--outline-variant);
  display: flex;
  align-items: center;
  gap: var(--abstand-s);
  flex-wrap: wrap;
}
/* Der Preisstand steht rechts — und nennt keine Minutenzahl (siehe
   preisstandHtml in js/seite-vergleich.js). */
.pv-preisstand {
  margin: 0 0 0 auto;
  font-family: var(--schrift-mono);
  font-size: var(--label-medium);
  color: var(--on-surface-variant);
}

/* Fußnote der Einstellungsseite (Kursstand): leiser als der Fließtext,
   aber nicht so klein, dass sie unlesbar wird. */
.einstellungs-fussnote {
  margin-top: var(--abstand-2xl);
  font-size: var(--body-small);
  color: var(--on-surface-variant);
  max-width: var(--lesebreite);
}

/* ---------- Nicht gefunden ----------
   Bewusst im Aufbau des Leerzustands gehalten: dieselbe Mitte, dieselbe
   Abstufung. Ein Fehler soll nicht wie ein Systemabsturz aussehen, sondern
   wie eine Sackgasse mit ausgeschildertem Rückweg. */

.nicht-gefunden {
  text-align: center;
  padding: var(--abstand-3xl) var(--abstand-l);
  color: var(--on-surface-variant);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--abstand-m);
}

.nicht-gefunden-symbol { width: 56px; height: 56px; color: var(--primary); }

/* Gewicht 600 war der einzige Zwischenwert im ganzen Projekt (sonst nur 400,
   500, 700). Die Größe trägt die Auszeichnung jetzt allein. */
.nicht-gefunden h1 {
  font-size: var(--headline-large);
  font-weight: 400;
  line-height: var(--zeile-eng);
  color: var(--on-surface);
}

.nicht-gefunden p { font-size: var(--body-large); max-width: 46ch; }

/* Der angefragte Pfad als Beleg — hilft beim Erkennen eines Tippfehlers.
   Bricht bei langen Adressen um, statt die Seite breit zu ziehen. */
.nicht-gefunden-pfad code {
  display: inline-block;
  max-width: 100%;
  overflow-wrap: anywhere;
  background: var(--surface-variant);
  color: var(--on-surface-variant);
  border-radius: var(--radius-s);
  padding: var(--abstand-xxs) var(--abstand-s);
  font-size: var(--body-small);
}

.nicht-gefunden-wege {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--abstand-s);
  margin-top: var(--abstand-l);
}

/* Die Regeln für „In Arbeit" (.in-arbeit*) sind am 05.08.2026 entfallen. Sie
   gehörten zu js/seite-in-arbeit.js — dem Platzhalter für /resin und
   /drucker, die es beide fertig gibt. Die Ansicht wurde von keiner Route mehr
   aufgerufen; das Stylesheet trug ihr Aussehen trotzdem weiter. */

/* ⛔ HIER STAND `.vergleich-hinweis` (bis 04.09.2026) — das Band „Du
   vergleichst unterschiedliche Materialien" über der Tabelle. Es ist mit dem
   Zusatz der Preissieger-Karte zu EINER Pille in der Kopfzeile
   zusammengefasst worden (`.pv-warnung`): Beide sagten dasselbe, einmal am
   Anfang und einmal am Ende der Seite. */

/* ---------- Vergleichstabelle ----------
   Ein gemeinsames Raster: erste Spalte die Zeilenbezeichnungen, danach je
   Produkt eine Spalte. Nur der Rahmen scrollt waagerecht — die Seite selbst
   bekommt keinen horizontalen Scrollbalken. */

.vt-rahmen {
  overflow-x: auto;
  max-width: 100%;
  /* Platz für den Fokusrahmen der Zellen beim Tastaturscrollen. */
  padding-bottom: var(--abstand-xs);
}

.vt-raster {
  display: grid;
  /* Bezeichnungsspalte fest, Produktspalten mindestens 200px breit. */
  grid-template-columns: 148px repeat(var(--spalten, 3), minmax(200px, 1fr));
  gap: var(--abstand-s);
  align-items: stretch;
  /* Erzwingt das Scrollen im Rahmen statt das Schrumpfen der Spalten. */
  min-width: min-content;
}

/* Die Bezeichnungsspalte bleibt beim Waagerechtscrollen stehen. Eigene
   Fläche, damit die Produktspalten nicht darunter durchscheinen. */
.vt-ecke,
.vt-label,
.vt-blocktitel span {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--surface);
}

.vt-label {
  display: flex;
  align-items: center;
  padding: var(--abstand-s);
  border-radius: var(--radius-s);
  font-size: var(--label-medium);
  font-weight: 500;
  color: var(--on-surface-variant);
}

.vt-zelle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--abstand-xs);
  min-width: 0;
  padding: var(--abstand-s);
  border-radius: var(--radius-s);
  font-size: var(--body-medium);
}
/* Werte stehen fett, Bezeichnungen gedämpft — so trennt die Vorlage die
   Auskunft von ihrer Beschriftung. */
.vt-wert { font-weight: 500; color: var(--on-surface); min-width: 0; }
/* ⚠️ Eine fehlende Angabe ist KEIN Wert: Sie tritt zurück, wird aber nicht
   weggelassen. Eine ausgelassene Zeile ließe offen, ob das Kriterium nicht
   gilt oder die Angabe fehlt. */
.vt-zelle--leer .vt-wert,
.vk-zeile dd.vt-zelle--leer .vt-wert {
  font-weight: 400;
  font-style: italic;
  color: var(--on-surface-variant);
}
.vt--abgesetzt { background: var(--surface-container-low); }

/* ⛔ DIE MARKE AM BESTEN WERT STEHT ALS WORT (04.09.2026). Bis dahin war es
   ein grünes Häkchen mit `title="Bester Wert"` — ein Titel erscheint auf
   Tastgeräten nie, und der Haken allein sagt nicht, worauf er sich bezieht.
   Farbe allein trägt in diesem Projekt ohnehin keine Aussage. */
.vt-beste {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  flex: 0 0 auto;
  padding: 1px var(--abstand-xs);
  border-radius: var(--radius-pille);
  /* ⚠️ Das Paar `--gruen-container` / `--gruen-inhalt` steht dafür bereit und
     ist als Paar gedacht — eine eigene Mischung aus `--balken-gruen` auf einer
     Flächenstufe wäre eine zweite, ungeprüfte Kombination für dieselbe
     Aussage. */
  background: var(--gruen-container);
  color: var(--gruen-inhalt);
  font-family: var(--schrift-mono);
  font-size: var(--label-small);
  letter-spacing: 0.04em;
}
.vt-beste svg { width: 12px; height: 12px; }

/* Blocküberschrift über die volle Rasterbreite; der Text selbst bleibt beim
   Scrollen links stehen.
   ⚠️ Versalien in Monospace nach der Bildvorlage: Der Blockkopf gliedert,
   er benennt kein Kriterium. Die Großschreibung entsteht über
   `text-transform` — im Markup steht gemischter Text, sonst buchstabieren
   Vorlesehilfen ihn teilweise. */
.vt-blocktitel {
  grid-column: 1 / -1;
  margin: var(--abstand-2xl) 0 var(--abstand-xs);
}
.vt-blocktitel span {
  display: inline-block;
  padding-right: var(--abstand-s);
  font-family: var(--schrift-mono);
  font-size: var(--label-medium);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--primary);
}

/* ---------- Vergleich als Karten (unter Tablet-Breite) ---------- */

.vergleich-karten {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-l);
}
.vk-karte {
  border-radius: var(--radius-m);
  background: var(--surface-container);
  padding: var(--abstand-xl);
}
.vk-blocktitel {
  font-size: var(--title-small);
  font-weight: 500;
  color: var(--primary);
  margin: var(--abstand-xl) 0 var(--abstand-xs);
}
.vk-blocktitel:first-child { margin-top: 0; }
.vk-zeilen { margin: 0; }
/* Wie in der Datenkarte der Detailansicht: eine Zeile je Angabe, getrennt
   über Zeilenabstand statt über eine Linie pro Zeile. */
.vk-zeile {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--abstand-m);
  padding: var(--abstand-s) 0;
  font-size: var(--body-medium);
}
.vk-zeile dt { color: var(--on-surface-variant); }
.vk-zeile dd {
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--abstand-xs);
  text-align: right;
}

/* Überlauf verhindern: Vergleichsspalten dürfen unter ihre Inhaltsbreite
   schrumpfen, sonst erzwingt der breiteste Zellinhalt einen Scrollbalken. */
.produkt-karte { min-width: 0; }

.produkt-karte {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--surface-container);
  border-radius: var(--radius-m);
  border: 1px solid var(--outline-variant);
  height: 100%;
}
/* ⛔ Der Preissieger hebt sich über KANTE und FARBE ab, nicht über die
   Fläche allein: Eine Flächenstufe trägt auf fast schwarzem Grund rund
   1,1:1 — dieselbe Begründung wie bei den Karten der Seite. */
.produkt-karte--sieger {
  border-color: var(--balken-gruen);
  box-shadow: inset 0 2px 0 0 var(--balken-gruen);
}

/* Die Marke steht IN der Karte, nicht als Balken darüber — sie soll den
   Produktkopf auszeichnen, nicht ihn abschneiden. */
.preissieger-balken {
  display: flex;
  align-items: center;
  gap: var(--abstand-xxs);
  margin: 0;
  padding: var(--abstand-s) var(--abstand-l) 0;
  color: var(--balken-gruen);
  font-family: var(--schrift-mono);
  font-size: var(--label-small);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.preissieger-balken svg { width: 12px; height: 12px; }
.produkt-karte--sieger .produkt-preis { color: var(--balken-gruen); }

/* Das × der Spalte. 28px liegen über den 24px aus WCAG 2.5.8; größer ginge
   nur auf Kosten des Produktnamens daneben, der bereits umbricht. */
.pv-weg {
  position: absolute;
  top: var(--abstand-xs);
  right: var(--abstand-xs);
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pille);
  background: none;
  color: var(--on-surface-variant);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}
.pv-weg:hover { background: var(--zustand-hover); color: var(--on-surface); }

/* Bild links neben Hersteller und Produktname, wie in der Vorlage. */
.pv-kopfzeile {
  display: flex;
  align-items: center;
  gap: var(--abstand-s);
  min-width: 0;
  /* Platz für das × in der Ecke — sonst läuft ein langer Produktname darunter. */
  padding-right: 24px;
}
.pv-kopfzeile > div { min-width: 0; }

/* Materialkennzeichen und Shop in einer Zeile. */
.pv-herkunft {
  display: flex;
  align-items: center;
  gap: var(--abstand-s);
  flex-wrap: wrap;
  margin: var(--abstand-xs) 0 0;
}
.pv-materialchip {
  padding: 1px var(--abstand-xs);
  border-radius: var(--radius-s);
  background: var(--surface-container-highest);
  color: var(--on-surface);
  font-family: var(--schrift-mono);
  font-size: var(--label-small);
}
.pv-shop { font-size: var(--label-medium); color: var(--on-surface-variant); }

.produkt-karte-inhalt {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-xs);
  padding: var(--abstand-l);
  flex: 1;
}
/* ⚠️ Klein und quadratisch neben dem Namen (Vorlage), nicht mehr als
   96px-Fläche über der ganzen Spaltenbreite: Der Kopf soll Name, Preis und
   Kaufweg tragen, das Bild ordnet nur zu. */
.produkt-karte-inhalt .produkt-bild {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-s);
  overflow: hidden;
}
/* `width: 100%` fehlte: Mit allein gesetzter Höhe behält ein liegendes Bild
   seine Eigenbreite und lief über die Spalte hinaus — das `overflow: hidden`
   des Elternteils schnitt es dann seitlich ab. Also derselbe Beschnitt wie
   bei cover, nur auf Umwegen. */
.produkt-karte-inhalt .produkt-bild img { width: 100%; height: 100%; object-fit: contain; }
.produkt-hersteller { font-size: var(--title-small); font-weight: 500; }
.produkt-name, .produkt-anbieter { font-size: var(--body-small); color: var(--on-surface-variant); }
.produkt-material { font-size: var(--title-medium); font-weight: 700; color: var(--primary); }
.produkt-karte-inhalt .abstandhalter { flex: 1; }
/* Gleiche Rolle wie auf der Kachel, deshalb dieselbe Größe: Der Kilopreis
   ist auch im Vergleich das dominante Element jeder Spalte.
   ⚠️ Serif wie jeder Preis der Seite, `nowrap` wie auf der Kachel: „ab
   40,90 CHF/kg" darf in einer 200px-Spalte nicht mitten in der Zahl brechen. */
.produkt-preis {
  margin-top: var(--abstand-s);
  font-family: var(--schrift-serif);
  font-size: var(--headline-medium);
  font-weight: 700;
  line-height: var(--zeile-eng);
  color: var(--primary);
  white-space: nowrap;
}
/* Die Herleitung: Menge, Rollenpreis, Versandvermerk — eine Mono-Zeile
   unter dem Preis, wie in der Vorlage. */
.produkt-rollendaten {
  font-family: var(--schrift-mono);
  font-size: var(--label-small);
  color: var(--on-surface-variant);
}
.produkt-rollendaten .versand-zusatz::before { content: " · "; }
/* Der Kaufweg schließt die Spalte ab und füllt sie. */
.produkt-karte-inhalt > .knopf { margin-top: var(--abstand-m); justify-content: center; }

/* ⛔ HIER STAND `.empfehlungs-karte` (bis 04.09.2026) — die Preissieger-Karte
   am Seitenende. Ihr Inhalt liegt jetzt dort, wo er hingehört: die
   Kennzeichnung an der Spalte (Marke, grüner Preis, gefüllter Knopf), der
   Zusatz zu gemischten Materialien in der Warnpille des Kopfes. Was eine
   Spalte nicht ausdrücken kann — „kein Wechselkurs" und „Preisgleichstand" —
   trägt jetzt `.pv-band`. */

/* ---------- Filament-Infos (Kachelraster) ---------- */

/* „Keine Materialart passt": Erklärblock mit Ausweg zum Navigator. */
.infos-kein-treffer {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-m);
  max-width: 520px;
}
.infos-kein-treffer h2 { font-size: var(--title-large); font-weight: 400; }
.infos-kein-treffer p { font-size: var(--body-large); color: var(--on-surface-variant); }

.infos-einstieg {
  display: flex;
  align-items: center;
  gap: var(--abstand-m);
  width: 100%;
  text-align: left;
  background: var(--primary-container);
  color: var(--on-primary-container);
  border: 0;
  border-radius: var(--radius-m);
  padding: var(--abstand-xl);
  cursor: pointer;
  margin: var(--abstand-2xl) 0;
  position: relative;
  transition: background-color var(--dauer-kurz) ease;
}
/* Zustandsebene wie beim gefüllten Knopf: Der Einstieg liegt auf einer
   Akzentfläche und kennt deshalb keine „Stufe höher". Kein Schatten, kein
   Anheben — er ist eine Fläche in der Seite, kein schwebendes Objekt. */
.infos-einstieg::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: currentColor;
  opacity: 0;
  transition: opacity var(--dauer-kurz) ease;
  pointer-events: none;
}
.infos-einstieg:hover::after { opacity: 0.08; }
.infos-einstieg:active::after { opacity: 0.12; }
.infos-einstieg .ie-text { flex: 1; }
.infos-einstieg h2 { font-size: var(--title-large); font-weight: 500; }
.infos-einstieg p { font-size: var(--body-medium); margin-top: var(--abstand-xs); }
.infos-einstieg svg { width: 24px; height: 24px; flex: 0 0 auto; }

/* Filterzeile: eigene Fläche wie die Werkzeugleisten der übrigen Ansichten,
   statt eines einzelnen Knopfes im Nichts. */
.infos-filterzeile {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--abstand-m);
  margin-bottom: var(--abstand-2xl);
  padding: var(--abstand-m) var(--abstand-l);
  border-radius: var(--radius-m);
  background: var(--surface-container);
}
.infos-filtertext {
  font-size: var(--body-medium);
  color: var(--on-surface-variant);
}

/* Jede Kategorie ein Abschnitt — mit demselben Abstandsmaß wie die
   Abschnitte der Startseite. */
.material-gruppe { padding-bottom: var(--abstand-2xl); }

/* Gruppentitel eröffnen einen Abschnitt: viel Luft davor, wenig danach.
   Genau dieser Unterschied macht die Zugehörigkeit sichtbar. */
.material-gruppe-titel {
  font-size: var(--title-large);
  font-weight: 500;
  color: var(--primary);
  margin: 0 0 var(--abstand-l);
}

/* Das Raster wächst mit der Breite, statt in einer schmalen Spalte zu
   stehen: Bei 1600px stehen fünf Kacheln nebeneinander, auf dem Handy eine.
   240px Mindestbreite, weil die Kacheln jetzt zwei Kennwertzeilen und die
   Angebotszeile tragen — mit den früheren 120px wäre jede Zeile umgebrochen. */
.material-raster {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: var(--abstand-l);
}

/* Zwei Ziele in einer Kachel: die Fläche führt zur Materialseite, die
   Angebotszeile in den Vergleich. Aufbau wie bei der Angebots-Kachel —
   Hauptlink mit ganzflächigem ::after, der zweite Link darüber. */
.material-kachel {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--abstand-s);
  padding: var(--abstand-l);
  /* background-COLOR statt Kurzform — siehe .ordner-karte. */
  background-color: var(--surface-container);
  border: 0;
  border-radius: var(--radius-m);
  transition: background-color var(--dauer-kurz) ease,
              background-image var(--dauer-kurz) ease,
              transform var(--dauer-kurz) ease;
}
.material-kachel:hover { transform: translateY(-2px); }
.material-kachel:active { transform: none; }

.mk-hauptlink { text-decoration: none; }
.mk-hauptlink::after { content: ""; position: absolute; inset: 0; }

/* Vergleichsschalter auf der Materialkachel.

   ⚠️ z-index über dem ::after des Hauptlinks. Das ::after liegt als
   durchsichtige Fläche über der GANZEN Kachel, damit man sie überall
   anklicken kann — ohne diese Zeile läge es auch über dem Knopf, und jeder
   Klick auf das Symbol führte in den Artikel statt in die Auswahl. Derselbe
   Grund wie bei .kachel-shop an der Angebots-Kachel. */
.mk-vergleich {
  position: absolute;
  top: var(--abstand-s);
  right: var(--abstand-s);
  z-index: 1;
}
/* Platz für den Knopf, damit er nicht auf dem Materialnamen sitzt. */
.material-kachel .mk-name { padding-right: 40px; }

.material-kachel .mk-name {
  display: block;
  font-size: var(--title-large);
  font-weight: 500;
  color: var(--primary);
}
.material-kachel .mk-temp { font-size: var(--body-small); color: var(--on-surface-variant); }

/* Kennwerte kompakt: dieselben Balken wie auf der Detailseite (dieselbe
   Funktion erzeugt sie), nur enger gesetzt. Farbe, Skala und Beschriftung
   bleiben unangetastet — daran wird hier nichts gedreht. */
.mk-kennwerte {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--abstand-xs);
}
.mk-kennwerte .balken-zeile {
  gap: var(--abstand-s);
  font-size: var(--label-medium);
}
.mk-kennwerte .balken-zeile--hoechstwert {
  padding: var(--abstand-xxs) var(--abstand-s);
}
.mk-kennwerte .balken { width: 56px; }
/* In der Kachel ist für die Klartext-Pille kein Platz; der Zahlenwert
   daneben bleibt und trägt die Aussage weiter — Farbe ist auch hier nie die
   einzige Information. */
.mk-kennwerte .balken-pille { display: none; }

/* Angebotszeile: liegt über der Klickfläche der Kachel und führt woanders
   hin als diese. */
.mk-angebote {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--abstand-s);
  margin-top: auto;
  padding-top: var(--abstand-s);
  color: inherit;
  text-decoration: none;
  font-size: var(--body-medium);
}
.mk-angebote:hover { text-decoration: underline; }
.mk-anzahl { color: var(--on-surface-variant); }
.mk-preis { font-weight: 700; color: var(--primary); white-space: nowrap; }

/* ---------- Material-Detailseite ---------- */

/* Alle Abschnittskästen — Kopf und jede Karte — haben dieselbe linke und
   rechte Kante. Begrenzt wird zusätzlich der FLIESSTEXT innerhalb der
   Kästen (.mk-satz, .beschreibung): Eine Lesespalte ist kein
   ausgefranster Rand. Die Punktelisten der Karten sind seit dem
   19.08.2026 ausgenommen — kurze Einzelpunkte sind kein Fließtext,
   siehe die Regel bei .punkteliste li.

   1120px statt der vollen 1600px-<main>-Breite — dasselbe Maß wie die
   Angebots-Detailseite (.detail). Ohne die Grenze zöge sich die Seite auf
   sehr breiten Bildschirmen unnötig auseinander, statt als zusammenhängender
   Block zu wirken. */
.material-seite { max-width: 1120px; margin: 0 auto; }
.material-seite h1 {
  font-size: var(--headline-large);
  line-height: var(--zeile-eng);
}

/* --- Kopf: Kategorie, Name und Einleitungssatz, Warnstufe daneben --- */

.material-kopf {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--abstand-l);
  align-items: start;
  padding: var(--abstand-l) 0 var(--abstand-xl);
}

@media (min-width: 900px) {
  /* auto statt einer festen Spur: Die Warnstufe nimmt, was ihr Text braucht,
     und gibt den Rest zurück. Fehlt sie ganz, füllt der Kopftext die Zeile
     von selbst — dafür braucht es keine Sonderregel. */
  .material-kopf {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--abstand-2xl);
  }
  .material-kopf .warnstufen-hinweis { max-width: 320px; }
}

.mk-satz {
  margin-top: var(--abstand-l);
  font-size: var(--body-large);
  line-height: var(--zeile-text);
  color: var(--on-surface-variant);
  max-width: var(--lesebreite);
}

/* Im Kopf schließt der Warnhinweis an, statt einen Abschnittsabstand nach
   unten aufzureißen. */
.material-kopf .warnstufen-hinweis { margin-bottom: 0; }

/* --- Wege am Ende der Seite --- */

.material-wege {
  display: flex;
  flex-wrap: wrap;
  gap: var(--abstand-m);
  padding-bottom: var(--abstand-2xl);
}

.material-leer { color: var(--on-surface-variant); }

.warnstufen-hinweis {
  display: flex;
  gap: var(--abstand-m);
  border-radius: var(--radius-s);
  padding: var(--abstand-l);
  margin-bottom: var(--abstand-2xl);
}
.warnstufen-hinweis svg { width: 24px; height: 24px; flex: 0 0 auto; }
.warnstufen-hinweis strong { display: block; font-size: var(--title-small); font-weight: 500; }
.warnstufen-hinweis p { font-size: var(--body-medium); margin-top: var(--abstand-xs); }
.warnstufen-hinweis--erhoeht { background: var(--error-container); color: var(--on-error-container); }
.warnstufen-hinweis--hardware { background: var(--tertiary-container); color: var(--on-tertiary-container); }

/* --- Abschnittskarten im Zuschnitt der Startseite --- */

.material-karte {
  background: var(--surface-container);
  border-radius: var(--radius-m);
  padding: var(--abstand-xl);
  /* Jede Karte ist ein eigener Abschnitt der Materialseite. */
  margin-bottom: var(--abstand-2xl);
}
.material-karte--gefahr {
  background: var(--error-container);
  color: var(--on-error-container);
}

/* Überschrift über Größe, nicht über Fettdruck — dieselbe Stufe wie die
   Abschnittsüberschriften der Startseite. */
.material-karte h2 {
  font-size: var(--headline-small);
  line-height: var(--zeile-eng);
  margin-bottom: var(--abstand-l);
}
.material-karte .beschreibung {
  font-size: var(--body-large);
  line-height: var(--zeile-text);
  max-width: var(--lesebreite);
}

/* --- Beschreibungskarte: Text + Kennwerte links, Beispielbild rechts ---

   Benannte Grid-Bereiche statt Spaltenreihenfolge über die Reihenfolge im
   Markup: Am Desktop stehen Text und Kennwerte in EINER Spalte
   übereinander, das Bild daneben über die volle Höhe beider — das braucht
   eine echte zweidimensionale Anordnung (das Bild spannt zwei Zeilen), die
   sich nicht mehr allein über die Dokumentreihenfolge lösen lässt. Auf
   schmalen Fenstern gilt eine ANDERE Reihenfolge (Text, Bild, Kennwerte) —
   ebenfalls über grid-template-areas, unabhängig von der Reihenfolge im
   Markup (die bleibt Text/Kennwerte/Bild, siehe seite-materialdetail.js). */
.mk-oben {
  display: grid;
  /* minmax(0, 1fr): Sonst ist die Spur nie schmaler als das längste Wort im
     Beschreibungstext, und „Kalibrierfehler" schob die Materialseite bei
     320px über den Bildschirmrand. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas:
    "text"
    "bild"
    "kennwerte";
  gap: var(--abstand-xl);
}
@media (min-width: 900px) {
  /* 1.75fr : 1fr ergibt rund 36 % der Kartenbreite fürs Bild — in der
     Mitte des gewünschten Drittels bis 40 %. */
  .mk-oben {
    grid-template-columns: 1.75fr 1fr;
    grid-template-areas:
      "text bild"
      "kennwerte bild";
    gap: var(--abstand-xl) var(--abstand-2xl);
  }
}

.mk-oben .beschreibung { grid-area: text; }
.mk-oben .kennwert-liste { grid-area: kennwerte; }

.mk-bild {
  grid-area: bild;
  border-radius: var(--radius-l);
  overflow: hidden;
  /* Fläche des Platzhalters; ein echtes Foto deckt sie vollständig ab. Eine
     der beiden dunkelsten Flächenstufen des Themas — deutlich abgesetzt von
     der Kartenfläche darum, aber weiterhin eine Oberflächenfarbe und kein
     harter Schwarzwert. In beiden Modi automatisch die passende Stufe, ohne
     eigenen Hexwert. */
  background: var(--surface-container-highest);
  /* Eigenes Seitenverhältnis nur auf schmalen Fenstern, wo das Bild eine
     eigene Zeile ohne Nachbarn hat und sonst je nach Inhalt (Symbol allein)
     auf null Höhe zusammenfiele. */
  aspect-ratio: 4 / 3;
}
@media (min-width: 900px) {
  /* Ab hier übernimmt die Zeilenhöhe von Text + Kennwerte die Höhe des
     Bildes (Grid-Standardverhalten „stretch" über zwei Zeilen hinweg) —
     das eigene Seitenverhältnis entfällt deshalb bewusst. min-height ist
     ein Auffangnetz für ungewöhnlich kurze Beschreibungstexte, nicht der
     Regelfall: Kennwerte allein füllen schon rund 200px. */
  .mk-bild {
    aspect-ratio: auto;
    min-height: 260px;
  }
}
.mk-bild img {
  width: 100%;
  height: 100%;
  /* Ein echtes Beispielfoto füllt die Fläche, wie auf der Startseite. Bei
     variabler Zielhöhe (siehe oben) schneidet cover statt zu verzerren —
     das Seitenverhältnis des FOTOS selbst bleibt dabei erhalten, nur der
     Bildausschnitt passt sich an. */
  object-fit: cover;
}

/* Solange kein Foto hinterlegt ist: Symbol mittig auf der Fläche, deutlich
   zurückgenommen — erkennbar als Platzhalter, nicht als Fehler. */
.mk-bild--platzhalter {
  display: flex;
  align-items: center;
  justify-content: center;
}
.mk-bild--platzhalter svg {
  width: 25%;
  height: 25%;
  color: var(--on-surface-variant);
  opacity: 0.7;
}

.punkteliste { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--abstand-m); }
/* ⚠️ Der Punkt hängt frei am Rand, der Eintrag selbst ist ein BLOCK — kein
   Flex-Behälter mit dem Text als zweitem Kind.

   Als Flex-Zeile wurde der Text zu einem anonymen Flex-Kind, und ein solches
   lässt sich nicht ansprechen: kein min-width: 0, kein flex-shrink. Es blieb
   damit auf seiner vollen Satzbreite stehen, und der Eintrag „Es findet keine
   Reichweitenmessung …“ war bei 390px Ansichtsbreite 370px breit in einer
   310px-Karte — die Datenschutzseite schob das ganze Dokument 20px über den
   Bildschirm. Als Block bricht der Text wie jeder andere Text. */
.punkteliste li {
  position: relative;
  padding-left: 1.1em;
  font-size: var(--body-large);
  line-height: var(--zeile-text);
  max-width: var(--lesebreite);
}
.punkteliste li::before { content: "•"; position: absolute; left: 0; top: 0; }
.punkteliste .schlagwort { font-weight: 500; }

/* In den Karten der Material-Detailseite (Praxishinweise, „So druckst du
   es“, „Gefahren & Risiken“) nutzen die Punkte die volle Kartenbreite und
   brechen erst am Kartenrand um (Entscheidung vom 19.08.2026): Es sind
   kurze Einzelpunkte, kein Fließtext — mit der Lesebreiten-Grenze brachen
   sie um, während rechts in der Karte Platz blieb.

   ⚠️ Die Grenze in .punkteliste li darüber bleibt trotzdem stehen:
   Dieselbe .punkteliste steht auch auf den Rechtstextseiten (Datenschutz,
   Affiliate), und dort ist sie Fließtext auf der 1120px-Textseite — die
   Zeilenbegrenzung dort ist die Entscheidung vom 11.08.2026 („Textseiten“)
   und wird hier nicht angetastet. Deshalb die Ausnahme am Kartencontainer
   statt einer Änderung der Grundregel. */
.material-karte .punkteliste li { max-width: none; }

/* --- Eigenschaften: mehrere schmale Listen nebeneinander ---

   Jede Gruppe für sich bleibt eine schmale Liste, KEIN zweispaltiges Raster
   innerhalb der Gruppe: Dort stand die linke Bezeichnung optisch näher an
   einer Nachbarspalte als am eigenen Wert, das Auge sprang zur falschen
   Zeile. Die Gruppen selbst stehen nebeneinander (.eigenschaften-spalten) —
   die Karte hat wieder die volle Breite für sich, seit die Kennwerte in die
   Beschreibungskarte gewandert sind.

   auto-fit statt einer festen Spaltenzahl: Bleiben einem Material nur ein
   oder zwei Gruppen (lückenhafte Daten), zieht sich KEINE leere Spalte über
   die Karte — die vorhandene(n) Gruppe(n) füllen die Breite gleichmäßig,
   jede bleibt aber über max-width auf eine lesbare Zeilenbreite begrenzt. */

.eigenschaften-spalten {
  display: grid;
  /* minmax(0, 1fr), nicht 1fr: Eine Rasterspur ohne Mindestangabe ist nie
     schmaler als der min-content ihres Inhalts. Die Eigenschaftszeilen
     drückten die Spur damit auf 318px in einer 310px-Karte. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--abstand-xl);
}
@media (min-width: 700px) {
  .eigenschaften-spalten {
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: var(--abstand-xl) var(--abstand-2xl);
    align-items: start;
  }
}

/* min-width: 0 gehört zum Rasterkind dazu: Ohne sie zieht die längste
   Beschriftung („Feuchtigkeitsbeständigkeit") die Gruppe über die
   Kartenbreite hinaus, weil Rasterkinder von sich aus nicht unter ihren
   min-content schrumpfen. */
.eigenschaften-gruppe { max-width: 480px; min-width: 0; }
.eigenschaften-gruppe .eigenschaften-liste { overflow-wrap: break-word; }

.eigenschaften-gruppe h3 {
  font-size: var(--title-small);
  font-weight: 500;
  color: var(--on-surface-variant);
  margin-bottom: var(--abstand-s);
}

/* Kennwerte (in der Beschreibungskarte) und Eigenschaften (Zeilen je Gruppe)
   teilen sich dieselbe Listenform: Beschriftung links, Wert bzw. Balken
   rechtsbündig, Trenner statt Zwischenraum. */
.kennwert-liste,
.eigenschaften-liste {
  margin: 0;
  display: flex;
  flex-direction: column;
}

/* Trenner statt Zwischenraum: Ohne jeden Abstand zwischen zwei Zeilen
   ordnet sich der Wert sonst leicht der FALSCHEN Bezeichnung zu. */
.kennwert-liste > *:not(:last-child),
.eigenschaften-liste > *:not(:last-child) {
  border-bottom: 1px solid var(--outline-variant);
}

/* Nur die gewöhnlichen Zeilen bekommen hier ihren Innenabstand — Stärke und
   Höchstwert bringen über ihre eigene Fläche (komponenten.css) schon einen
   mit, ein zweiter verschöbe dort nur den Wert. */
.kennwert-liste > *:not(.balken-zeile--hoechstwert):not(.sterne-zeile--staerke),
.eigenschaften-liste > *:not(.balken-zeile--hoechstwert):not(.sterne-zeile--staerke) {
  padding: var(--abstand-s) 0;
}

/* Stärke und Höchstwert tragen ihre eigene Fläche und Rundung — eine
   zusätzliche Trennlinie wirkte daneben nur wie ein Fehler in der Fläche,
   deshalb hier keine. Der waagerechte Innenabstand schöbe ihren Wert nach
   innen; der gleich große negative Außenabstand gleicht das aus, damit er
   auf derselben Kante wie alle anderen Werte steht — auch die Pillenzeilen
   rücken also nicht ein. */
.kennwert-liste > .balken-zeile--hoechstwert,
.kennwert-liste > .sterne-zeile--staerke,
.eigenschaften-liste > .balken-zeile--hoechstwert,
.eigenschaften-liste > .sterne-zeile--staerke {
  margin-inline: calc(var(--abstand-s) * -1);
  border-bottom: 0;
}

/* ---------- Material-Navigator ---------- */

.navigator-seite {
  max-width: 560px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--abstand-l);
  text-align: center;
}

.navigator-leiste {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Kopfzeile des Ablaufs, danach beginnt die Frage — ein Abschnittswechsel. */
  margin-bottom: var(--abstand-2xl);
}
.navigator-leiste h1 { font-size: var(--title-large); line-height: var(--zeile-normal); }

.fortschritt-text { font-size: var(--label-large); font-weight: 500; color: var(--on-surface-variant); }
.fortschritt-punkte { display: flex; gap: var(--abstand-xs); justify-content: center; }
.fortschritt-punkt {
  width: 8px; height: 8px;
  border-radius: var(--radius-pille);
  background: var(--outline-variant);
  transition: width var(--dauer-mittel) ease, background-color var(--dauer-mittel) ease;
}
.fortschritt-punkt--aktiv { width: 16px; background: var(--primary); }

/* Die Frage ist das Element, um das es auf dieser Seite geht — sie bekommt
   die Größe eines Seitentitels. Der Untertitel tritt deutlich zurück. */
.frage-titel { font-size: var(--headline-small); line-height: var(--zeile-eng); }
.frage-untertitel { font-size: var(--body-medium); font-weight: 400; color: var(--on-surface-variant); }

.antwort-karte {
  width: 100%;
  border: 0;
  border-radius: var(--radius-m);
  /* background-COLOR statt Kurzform — siehe .ordner-karte. */
  background-color: var(--surface-container);
  color: var(--on-surface);
  padding: var(--abstand-l) var(--abstand-xl);
  cursor: pointer;
  text-align: left;
  font-size: var(--body-large);
  transition: background-color var(--dauer-kurz) ease,
              background-image var(--dauer-kurz) ease,
              color var(--dauer-kurz) ease;
}
/* Zustandsebene statt Anheben — dieselbe Rückmeldung wie bei Kachel und
   Materialkachel; sie kommt aus der gemeinsamen Regel in komponenten.css. */
/* Gewählt: Akzentfläche. Kein Fettdruck — der bleibt dem Preis vorbehalten,
   und die Fläche ist ohnehin das stärkere Signal. */
.antwort-karte[aria-pressed="true"] {
  background: var(--primary);
  color: var(--on-primary);
}
.antwort-karte[aria-pressed="true"]:hover { background: var(--primary); }
.antwort-karte .erklaerung {
  display: block;
  font-size: var(--body-small);
  font-weight: 400;
  opacity: 0.8;
  margin-top: var(--abstand-xs);
}

.navigator-navigation { display: flex; gap: var(--abstand-m); justify-content: center; }

.vorschlag-karte {
  width: 100%;
  background: var(--surface-container);
  border-radius: var(--radius-m);
  padding: var(--abstand-xl);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--abstand-s);
}
.vorschlag-karte h3 { font-size: var(--title-large); font-weight: 500; color: var(--primary); }
.vorschlag-karte p { font-size: var(--body-medium); }

/* Die beiden Wege einer Vorschlagskarte: in den Artikel und in die Angebote
   (20.09.2026, mit dem Wegfall des Sammelknopfs „Filter übernehmen").

   ⚠️ `flex-wrap`, nicht `nowrap`: Beide Beschriftungen tragen den
   Materialnamen, und „Water-Washable-Angebote ansehen" neben „Infos zu
   Water-Washable" sprengt jede schmale Karte. Umbrechen ist hier richtig —
   es sind zwei gleichrangige Wege, keine Reihe, deren Ordnung etwas aussagt.

   ⚠️ Kein `width: 100%` an den Knöpfen: `.vorschlag-karte` zentriert ihre
   Kinder (`align-items: center`), die Wege sollen so breit sein wie ihr Text.
   Gestapelt stünden sie dadurch beide mittig untereinander. */
.vk-wege {
  display: flex;
  flex-wrap: wrap;
  gap: var(--abstand-s);
  justify-content: center;
}

/* Der hervorgehobene Vorschlag trägt eine eigene Akzentfläche — die
   zusätzliche Umrandung war doppelt gemoppelt. */
.vorschlag-karte--optimal {
  background: var(--secondary-container);
  color: var(--on-secondary-container);
}
.vorschlag-karte--optimal .vk-obertitel { font-size: var(--title-small); font-weight: 500; }
.vorschlag-karte--optimal h3 { color: inherit; }
.vorschlag-karte--optimal .vk-hinweis { font-size: var(--body-small); }

.navigator-fussnote { font-size: var(--body-small); color: var(--on-surface-variant); }

/* ---------- Farbfilter ----------
   Die Farbfläche eines Feldes trägt den echten Produktfarbwert (farbeHex) —
   die einzige Stelle im Projekt, an der bewusst kein Theme-Token die Farbe
   bestimmt. Name und Trefferzahl stehen daneben als Text: Die Farbe ist nie
   die einzige Information. */

/* --- Gliederung: Schnellauswahl, Texteingabe, Farbkreis, Toleranz ---
   Vier Bedienwege in einem Abschnitt. Sie stehen als eigene Blöcke
   untereinander, jeder mit kleiner Beschriftung und gleichem Abstand.
   Bewusst normaler Blockfluss statt Flexbox: die Feldreihe, der Farbkreis
   (Breite 100 %, max. 200px) und die Knöpfe behalten so ihr natürliches
   Breitenverhalten. */

.farbblock + .farbblock { margin-top: var(--abstand-m); }

/* ⛔ HIER STAND `.farbblock-titel` („Häufige Farben", „Andere Farbe suchen")
   — mit der Bildvorlage vom 25.09.2026 entfallen: Chips und Felder
   erklären sich selbst, die Namen stehen als `aria-label` am Element. */

/* Hex-Feld und Namenssuche in EINER Zeile (Bildvorlage 25.09.2026). Die
   Vorschlagsliste steht im Fluss unter der Suche, in deren Spalte. */
.farb-eingaben {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  gap: var(--abstand-m);
  align-items: start;
}
@container ef-inhalt (max-width: 479px) {
  .farb-eingaben { grid-template-columns: minmax(0, 1fr); }
}

/* Die Vorschlagsliste liegt IM FLUSS unter dem Feld — kein schwebendes Menü.
   Sie kann dadurch nichts verdecken. Das frühere `position: relative` an
   diesem Element war der Anker eines nie umgesetzten Aufklappmenüs und hatte
   die gegenteilige Wirkung: Als einziges positioniertes Element des
   Abschnitts wurde es samt Schatten und Fokusrahmen ÜBER der unmittelbar
   anschließenden Feldreihe gezeichnet. Es bleibt draußen. */
.farbsuche .filter-suchfeld { margin-top: 0; }

/* Eigene Flächenstufe statt Rahmen und Schatten: Die Liste liegt im Fluss,
   sie schwebt nicht — ein Schatten hätte hier nichts zu überlagern. */
.farbvorschlaege {
  list-style: none;
  margin: var(--abstand-s) 0 0;
  padding: var(--abstand-xs);
  border-radius: var(--radius-s);
  background: var(--surface-container-low);
  /* ⚠️ Ohne eigene Höhengrenze (25.09.2026): Die Liste ist in
     PD.filter.farbvorschlaege auf MAX_FARBVORSCHLAEGE gedeckelt und rollt
     mit dem Fenster — ein zweiter Rollbalken im rollenden Körper war einer
     der „unnötigen". */
}
.farbvorschlaege[hidden] { display: none; }

.farbvorschlag {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--abstand-s);
  padding: var(--abstand-s) var(--abstand-m);
  border-radius: var(--radius-s);
  cursor: pointer;
  font-size: var(--body-medium);
}
.farbvorschlag:hover { background: var(--zustand-hover); }
/* Markierter Vorschlag: Fläche UND kräftigere Schrift — die Tastaturauswahl
   hängt nie allein am Farbton. */
.farbvorschlag--aktiv {
  background: var(--secondary-container);
  color: var(--on-secondary-container);
  font-weight: 500;
}
.farbvorschlag-anzahl { font-size: var(--label-medium); opacity: 0.8; }

/* Farbfeld in derselben Form wie Material und Gewicht, mit Trefferzahl.

   ⚠️ 0,88 statt 0,75 (10.08.2026). Der Ruhezustand lag mit 0,75 bei 4,90:1
   und damit knapp über der Grenze — GEWÄHLT kippte er: Der gedrückte Chip
   setzt --on-secondary-container auf --secondary-container, und 0,75 Deckkraft
   machen daraus 4,31:1, unter den 4,5:1 für Normaltext. Es fällt nicht auf,
   weil nur die ZAHL betroffen ist; die Beschriftung daneben steht bei 8,14:1.
   Mit 0,88 trägt auch der gewählte Zustand. Die Zahl bleibt sichtbar
   nachgeordnet — dafür genügt der kleinere Schriftgrad. */
.farbchip .chip-anzahl,
.chip-anzahl {
  margin-left: var(--abstand-xs);
  font-size: var(--label-medium);
  opacity: 0.88;
}
.chip:disabled { opacity: 0.38; cursor: default; }
/* ⛔ HIER STAND `.farbchip--klapp` (bis 04.09.2026) — der Klapp-Pfeil am Knopf
   „Farbkreis öffnen/schließen". Der Knopf ist mit dem Klappen selbst
   entfallen: Der Kreis steht im Reiter „Material und Farbe" immer offen
   (js/seite-liste.js, farbAbschnittHtml). Wer die Regel stehenließe, hielte
   einen Zustand fest, den es nicht mehr gibt. */

/*
 * Der Farbpunkt am Chip: die Farbe SEHEN, nicht nur lesen.
 *
 * flex-shrink: 0, weil er in einer Chip-Reihe sonst als erstes nachgibt und
 * aus dem Kreis eine Linse wird. Die Kante hält ihn auch vor gleichfarbigem
 * Grund sichtbar — ein weißer Punkt auf heller Fläche wäre sonst kein Punkt.
 *
 * ⚠️ Der Ring trägt seit dem 11.08.2026 --outline statt --outline-variant.
 * Genau die Aufgabe, die der Absatz darüber beschreibt, konnte er vorher
 * nicht erfüllen: Als Trennlinienfarbe stand er bei 1,95:1 hell und 1,73:1
 * dunkel gegen die Fläche darunter — bei einem weißen oder sehr hellen
 * Farbwert war der Punkt damit weder als Punkt noch als Farbe zu erkennen.
 * Jetzt 4,69 / 5,50:1. Er ist nicht Zierde, sondern das, was die Farbprobe
 * überhaupt zu einer abgegrenzten Fläche macht (WCAG 1.4.11).
 */
.farbpunkt {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
  border-radius: 50%;
  border: 1px solid var(--outline);
  background-color: var(--surface-container-lowest);
}
/* Kein Farbwert hinterlegt (im Bestand: „Transparent"): Schachbrett wie bei
   der Vorschaufläche — geraten wird auch hier nicht. */
.farbpunkt--ohne {
  background-image:
    linear-gradient(45deg, var(--outline-variant) 25%, transparent 25%),
    linear-gradient(-45deg, var(--outline-variant) 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, var(--outline-variant) 75%),
    linear-gradient(-45deg, transparent 75%, var(--outline-variant) 75%);
  background-size: 8px 8px;
  background-position: 0 0, 0 4px, 4px -4px, -4px 0;
}
/* Die Sammelauswahl steht für viele Töne — ein Verlauf über den ganzen
   Farbkreis sagt das, ohne eine einzelne Farbe zu behaupten. */
.farbpunkt--sammel,
.farbflaeche--sammel {
  background-image: conic-gradient(
    #c62828, #ef6c00, #f9a825, #2e7d32, #00acc1,
    #1565c0, #6a1b9a, #d81b60, #c62828
  );
}

/* --- Farbkreis: eine Zeichenfläche statt vieler Einzelelemente ---

   ⛔ ZWEISPALTIG NACH DER BILDVORLAGE (04.09.2026): links der Kreis, rechts
   Hexwert, Toleranz, Schnellauswahl und die Namenssuche. Der Reiter
   „Material und Farbe" gibt dem Kreis rund 1000px Fläche — das ist der
   Grund, warum er nicht mehr zugeklappt ist.

   ⚠️ `minmax(0, …)` in beiden Spuren: Eine Rasterspur wird von sich aus nie
   schmaler als ihr Inhalt, und der Kreis trägt eine feste Mindestbreite —
   ohne die Null drückte er die Spalte über den Dialog hinaus. Dieselbe Falle
   wie im Listenraster. */
.farb-raster {
  display: grid;
  grid-template-columns: minmax(0, 280px) minmax(0, 1fr);
  gap: var(--abstand-2xl);
  align-items: start;
}
.farb-steuer { min-width: 0; }
.farb-steuer > * + * { margin-top: var(--abstand-l); }
/* ⛔ HIER STAND `.farbkreis-hinweis` — entfallen am 25.09.2026 (Bildvorlage). */

.farbkreis-bereich {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--abstand-l);
  min-width: 0;
}

.farbkreis {
  display: block;
  width: 100%;
  /* Nie größer als nötig — im Reiter darf er deutlich größer sein als in der
     früheren 300px-Spalte, aber ein Kreis über 280px trägt keine zusätzliche
     Genauigkeit mehr: Die Auflösung der Auswahl hängt am Zeiger, nicht an
     der Fläche. */
  max-width: 280px;
  height: auto;
  aspect-ratio: 1;
  border-radius: 50%;
  cursor: crosshair;
  /* Innenrand, damit sich der Kreis vom Untergrund abhebt — die hellen
     Randtöne verschwänden sonst in der Karte. */
  box-shadow: inset 0 0 0 1px var(--outline);
  /* Wischgesten sollen den Kreis bedienen, nicht die Seite scrollen. */
  touch-action: none;
}
.farbkreis:focus-visible { outline-offset: 4px; }

/* --- Helligkeitsleiste --- */

.farbhelligkeit {
  display: block;
  width: 100%;
  max-width: 280px;
}
.farbhelligkeit input[type="range"] { width: 100%; }

/* Unter 720px Fläche stapeln Kreis und Steuerung. Eine CONTAINER-Abfrage,
   keine Medienabfrage: Maßgeblich ist die Breite der Reiterfläche, und die
   hängt davon ab, ob die Reiterspalte daneben steht oder darüber. */
@container ef-inhalt (max-width: 719px) {
  .farb-raster { grid-template-columns: minmax(0, 1fr); }
  .farbkreis-bereich { align-items: flex-start; }
}

/* ⛔ HIER STAND DIE VORSCHAUFLÄCHE (`.farbvorschau*`) — entfallen am
   25.09.2026 mit der Bildvorlage. Die gewählte Farbe zeigen die gefüllte
   Scheibe im Kreis, das Hex-Feld und der Chip im Kopf des Fensters. */

/* Transparent hat keinen Farbwert: Schachbrett als Sinnbild statt Fläche. */
.farbflaeche--transparent {
  background-image:
    linear-gradient(45deg, var(--outline-variant) 25%, transparent 25%),
    linear-gradient(-45deg, var(--outline-variant) 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, var(--outline-variant) 75%),
    linear-gradient(-45deg, transparent 75%, var(--outline-variant) 75%);
  background-size: 10px 10px;
  background-position: 0 0, 0 5px, 5px -5px, -5px 0;
  background-color: var(--surface-container-lowest);
}

/* --- Hex-Eingabe --- */

/* Eingabefeld, also --outline: Die Kante ist das Einzige, was das Feld als
   Bedienelement kenntlich macht, und muss 3:1 erfüllen. --outline-variant
   kam hell nur auf 1,6:1 — das war der einzige echte Verstoß gegen WCAG
   1.4.11 im Bestand. */
/* ⚠️ Seit dem 25.09.2026 in Fläche und Maß wie die Namenssuche daneben
   (`.filter-suchfeld`) — die Bildvorlage stellt beide als gleiche Felder in
   eine Zeile. */
.farbe-frei-hex { display: block; }
.farbe-frei-hex input {
  width: 100%;
  padding: var(--abstand-s) var(--abstand-m);
  border: 1px solid var(--outline);
  border-radius: var(--radius-s);
  background: var(--surface-container-lowest);
  color: var(--on-surface);
  font-size: var(--body-medium);
}
/* Ohne eigene Regel gilt Chromes Vorgabe #757575 — auf dieser Feldfläche
   4,19:1 und damit unter 4,5:1. Begründung ausführlich bei
   .drucker-suchfeld::placeholder in css/layout.css; es sind die beiden
   einzigen Eingabefelder des Projekts, denen die Regel fehlte. */
.farbe-frei-hex input::placeholder { color: var(--on-surface-variant); }

.farbe-frei-hex--ungueltig { border-color: var(--error) !important; }

/* --- Toleranzregler ---
   Eigener Block innerhalb des Kreisbereichs: Trennlinie darüber, volle
   Breite. Ohne die Breitenangabe bliebe der Regler ein zusammengeschobenes
   Flex-Element (der Kreisbereich zentriert seine Kinder) und stünde
   schmaler da als alles andere im Abschnitt. */

/* ⚠️ Seit dem 25.09.2026 ohne Trennlinie und immer sichtbar (Bildvorlage):
   Kopfzeile „Toleranz … Wert", Regler, Skala „genau / ähnlich". Ohne
   Hex-Auswahl trägt der Block `.ef-aus` und steht gedämpft da. */
.farb-toleranz { width: 100%; }
.farb-toleranz-kopf {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--abstand-s);
  margin-bottom: var(--abstand-xs);
}
.farb-toleranz-kopf h3 {
  margin: 0;
  font-family: var(--schrift);
  font-size: var(--body-medium);
  font-weight: 400;
  color: var(--on-surface);
}
.farb-toleranz-wert {
  font-family: var(--schrift-mono);
  font-size: var(--label-medium);
  color: var(--on-surface-variant);
}
.ef-aus .farb-toleranz-kopf h3 { color: var(--on-surface-variant); }
/* Keine Blase über dem Griff — der Wert steht in der Kopfzeile. Der Platz,
   den `.regler` für die Blase freihält, entfällt deshalb hier. */
.farb-toleranz .regler { padding-top: 0; }

.farb-skala {
  display: flex;
  justify-content: space-between;
  margin-top: var(--abstand-xs);
  font-size: var(--label-medium);
  color: var(--on-surface-variant);
}
/* ⛔ HIER STAND `.farb-treffer` — entfallen am 25.09.2026 (Bildvorlage). */

/* ---------- Kompatibilität auf der Detailseite ----------
   Badge plus Begründung im Klartext — hier ist Platz für den Grund, den die
   Kachel nur als Stufe zeigen kann. */

.kompatibilitaets-block {
  margin-top: var(--abstand-l);
  padding: var(--abstand-l);
  border-radius: var(--radius-s);
  background: var(--surface-container-high);
}

.kompatibilitaets-kopf {
  display: flex;
  align-items: center;
  gap: var(--abstand-s);
  flex-wrap: wrap;
}

.kompatibilitaets-drucker {
  font-size: var(--body-small);
  color: var(--on-surface-variant);
}

.kompatibilitaets-gruende {
  margin: var(--abstand-s) 0 0;
  padding-left: var(--abstand-l);
  font-size: var(--body-medium);
  color: var(--on-surface-variant);
}
.kompatibilitaets-gruende li + li { margin-top: var(--abstand-xs); }

/* ---------- Einstellungen und Über PrintSupport (Seiten des Benutzermenüs) ----------
   Beide sind reine Lesespalten: eine begrenzte Zeilenlänge liest sich besser
   als volle Fensterbreite.

   Die 720px hier greifen nur im Tablet-Band (640–899px). Ab 900px begrenzt
   und zentriert die Regel main[data-ansicht=…] in css/layout.css die ganze
   Seite — Überschrift und Einleitung eingeschlossen, die hier außerhalb des
   Containers stehen. Wer die Breite am Desktop ändern will, tut das dort und
   nicht hier, sonst stehen zwei Werte gegeneinander. */

/* ⚠️ `.ueber-seite` stand hier bis zum 11.08.2026 mit in der Liste. Die
   Textseiten tragen jetzt `.textseite` (Abschnitt weiter unten) und hängen
   damit nicht mehr an dieser geteilten Regel — /einstellungen bleibt allein
   zurück und unverändert. */
.einstellungs-seite {
  display: flex;
  flex-direction: column;
  /* Jede Karte ist ein Abschnitt für sich. */
  gap: var(--abstand-2xl);
  max-width: 720px;
  margin-top: var(--abstand-2xl);
}

.einstellungs-karte > h2,
.ueber-karte > h2 {
  font-size: var(--title-large);
  font-weight: 500;
  margin-bottom: var(--abstand-m);
}

.einstellungs-karte > p,
.ueber-karte > p { max-width: var(--lesebreite); }
.einstellungs-karte > p + p,
.ueber-karte > p + p { margin-top: var(--abstand-m); }

.einstellungs-hinweis,
.ueber-hinweis {
  font-size: var(--body-medium);
  color: var(--on-surface-variant);
}

/* Hinweiszeilen mit Symbol: das Icon bleibt klein und schrumpft nicht. */
.ueber-hinweis {
  display: flex;
  align-items: flex-start;
  gap: var(--abstand-s);
}
.ueber-hinweis svg {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  margin-top: var(--abstand-xxs);
}

/* --- Erscheinungsbild: zwei sich ausschließende Werte als Segmentwahl --- */

/* ⚠️ Hier standen bis zum 03.09.2026 .theme-wahl und .theme-option — die
   Radiogruppe „Hell / Dunkel" auf /einstellungen. Sie sind mit der Wahl
   entfallen; die Seite ist dauerhaft dunkel (siehe css/theme.css). */

/* --- Auskunft über den lokalen Speicher --- */

.einstellungs-liste {
  margin: var(--abstand-l) 0;
  display: flex;
  flex-direction: column;
  gap: var(--abstand-s);
}
.einstellungs-zeile {
  display: flex;
  justify-content: space-between;
  gap: var(--abstand-m);
  font-size: var(--body-medium);
}
.einstellungs-zeile dt { color: var(--on-surface-variant); }
.einstellungs-zeile dd { margin: 0; }

/* --- Rechtstexte: noch nicht befüllt --- */

.rechtstext-liste {
  list-style: none;
  margin: var(--abstand-m) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--abstand-m);
}

/* ⛔ DIE GESTRICHELTE KONTUR IST AM 16.09.2026 ENTFALLEN. Sie sagte „noch
   nicht befüllt" und war damit Information, keine Verzierung — genau deshalb
   musste sie weg: Die drei Texte sind seit dem 15./16.09.2026 vollständig,
   und eine gestrichelte Linie hätte als einzige Stelle der Seite weiter das
   Gegenteil behauptet, ohne dass ein Wort dazu dastand. Dieselbe Sprache wie
   bei den Platzhaltern im Text — und die gibt es dort ebenfalls nicht mehr. */
.rechtstext-zeile {
  padding: var(--abstand-l);
  border: 1px solid var(--outline-variant);
  border-radius: var(--radius-s);
  background: var(--surface-container);
}

.rechtstext-kopf {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--abstand-s);
  flex-wrap: wrap;
}

.rechtstext-titel { font-size: var(--title-small); font-weight: 500; }
a.rechtstext-titel { text-decoration: underline; }

/* --- Rechtstextseiten: Entwurfshinweis und Platzhalter --- */

/* Der Entwurfsbalken muss die Seite dominieren, solange die Texte ungeprüft
   sind. Fehlerrolle statt Warnorange: Das ist kein Nebenhinweis, sondern ein
   Grund, die Seite nicht öffentlich zu stellen. */
/* Die Umrandung bleibt — anders als bei den Karten ist sie hier Teil der
   Aussage: Der Balken soll wie ein Aufkleber wirken, der nicht zum Entwurf
   gehört. Sie läuft in currentColor und erfüllt damit dasselbe
   Kontrastverhältnis wie der Text darin. */
.entwurf-hinweis {
  display: flex;
  gap: var(--abstand-m);
  padding: var(--abstand-xl);
  margin-bottom: var(--abstand-2xl);
  border-radius: var(--radius-m);
  background: var(--error-container);
  color: var(--on-error-container);
  border: 1px solid currentColor;
}

.entwurf-hinweis svg {
  width: 24px; height: 24px;
  flex: 0 0 auto;
}

.entwurf-hinweis strong { font-size: var(--title-small); }

.entwurf-hinweis p {
  margin-top: var(--abstand-xs);
  font-size: var(--body-large);
  line-height: var(--zeile-text);
  /* Wie jeder Fließtext dieser Seiten begrenzt. Ohne die Grenze lief der
     Absatz auf die volle Balkenbreite — bei 618px Spalte 500px, auf der
     1120px-Seite rund 1000px. */
  max-width: var(--lesebreite);
}

/* Platzhalter tragen die eckigen Klammern im Text — die Auszeichnung hier ist
   die zweite Sicherung, nicht die einzige. Deshalb zusätzlich zur Fläche eine
   gestrichelte Kontur: erkennbar auch ohne Farbwahrnehmung. */
.platzhalter,
.platzhalter-beispiel {
  background: var(--surface-container-highest);
  color: var(--on-surface);
  border: 1px dashed var(--outline);
  border-radius: var(--radius-s);
  padding: 0 var(--abstand-xs);
  font-style: italic;
}

/* Die Angaben der Rechtstexte sind länger als die kurzen Werte der
   Einstellungsseite. Nebeneinander liefen sie auf einen Rest von zwei Wörtern
   zusammen — hier steht der Wert deshalb unter seiner Beschriftung. */
.ueber-karte .einstellungs-zeile {
  flex-direction: column;
  align-items: flex-start;
  gap: var(--abstand-xs);
}

.pruefpunkt-liste {
  list-style: none;
  margin: var(--abstand-m) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--abstand-s);
}

.pruefpunkt-liste li {
  display: flex;
  gap: var(--abstand-s);
  font-size: var(--body-large);
  line-height: var(--zeile-text);
  /* ⚠️ Seit dem 11.08.2026 begrenzt — vorher trug diese Zeile als EINZIGER
     Fließtextbaustein der Textseiten gar keine Grenze und lief auf die volle
     Karteninnenbreite. Bei 618px Spalte fiel das nicht auf (gemessen 538,5
     gegen 586,5 Begrenzung bei .punkteliste li); auf der 1120px-Seite wären
     daraus rund 1040px und damit über 120 Zeichen je Zeile geworden. */
  max-width: var(--lesebreite);
}

.pruefpunkt-liste li::before {
  content: "☐";
  flex: 0 0 auto;
}

/* Aufzählung ohne Schlagwort-Spalte (Datenschutz, Affiliate). */
.punkteliste--schlicht { gap: var(--abstand-s); }

/* Kernaussage der Affiliate-Seite: Sie soll gelesen werden, auch wenn sonst
   nichts gelesen wird. */
.affiliate-kernsatz {
  font-size: var(--title-medium);
  line-height: var(--zeile-normal);
  font-weight: 500;
  max-width: var(--lesebreite);
}

/* --- Beispieldaten-Hinweis über der Angebotsliste (28.09.2026) ---

   Steht VOR dem Werbeband und ist bewusst lauter: eigene Fläche -high statt
   der Kartenstufe, Fließtextgröße (16px) statt der kleinen Stufe (13px),
   Kante links.

   ⛔ DIE AUSSAGE TRÄGT DAS WORT „Beispieldaten:" SAMT WARNSYMBOL, nicht die
   Farbe. Kante, Symbol und Vorsatz stehen in --warnung-erhoeht (auf -high
   geprüft in pruefeTextAufFlaeche), der Satz in --on-surface.

   ⚠️ Sein Abstand nach unten hängt am Hinweis selbst — steht der Schalter
   aus, wird er nicht gezeichnet, und es bleibt keine Lücke.

   ⚠️ `min-width: 0` und `overflow-wrap` am Text: Bei doppelter Grundschrift
   auf 390px darf kein langes Wort die Zeile sprengen (Prüffall). */
.beispieldaten-hinweis {
  display: flex;
  align-items: flex-start;
  gap: var(--abstand-s);
  margin-bottom: var(--abstand-m);
  padding: var(--abstand-m);
  border-radius: var(--radius-s);
  border-left: 4px solid var(--warnung-erhoeht);
  background: var(--surface-container-high);
  font-size: var(--body-large);
  line-height: var(--zeile-normal);
  color: var(--on-surface);
}

.beispieldaten-hinweis svg {
  width: 20px; height: 20px;
  flex: 0 0 auto;
  color: var(--warnung-erhoeht);
}

.beispieldaten-hinweis span { min-width: 0; overflow-wrap: anywhere; }

.beispieldaten-hinweis strong { font-weight: 600; color: var(--warnung-erhoeht); }

/* --- Werbekennzeichnung über der Angebotsliste --- */

/* Sitzt direkt über der Liste und damit über jedem „Zum Shop"-Knopf.
   Zurückhaltend gestaltet, aber dauerhaft: Es gibt kein Schließen. */
.werbehinweis {
  display: flex;
  align-items: flex-start;
  gap: var(--abstand-s);
  margin-bottom: var(--abstand-l);
  padding: var(--abstand-s) var(--abstand-m);
  border-radius: var(--radius-s);
  background: var(--surface-container);
  font-size: var(--body-small);
  color: var(--on-surface-variant);
  line-height: var(--zeile-normal);
}

.werbehinweis svg {
  width: 16px; height: 16px;
  flex: 0 0 auto;
  margin-top: var(--abstand-xxs);
}

.werbehinweis a { text-decoration: underline; white-space: nowrap; }

/* Zwei Längen derselben Kennzeichnung — die kurze nur auf dem Handy.
   Gekürzt sind allein die beiden Vertrauenssätze; der Kernsatz (ob die Links
   Werbelinks sind und ob PrintSupport daran verdient) und der Verweis „Mehr
   dazu" stehen in beiden Längen. Die Begründung dazu in js/seite-liste.js.

   ⚠️ ZWEI ACHSEN, NICHT EINE: Diese beiden Klassen unterscheiden die LÄNGE
   (Fensterbreite), `.af-an`/`.af-aus` in css/komponenten.css die AUSSAGE
   (fließt eine Provision?). Jede Länge gibt es deshalb zweimal, und das
   Werbeband trägt vier Fassungen. Wer eine davon kürzt, kürzt die andere
   mit — sonst sagt das Handy etwas anderes als der Desktop. */
.werbehinweis-kurz { display: none; }

@media (max-width: 639px) {
  .werbehinweis-kurz { display: inline; }
  .werbehinweis-lang { display: none; }
}

/* --- Hinweis auf Ähnlichkeitstreffer über der Liste ---

   Erscheint nur, wenn die Liste ausschließlich aus Tippfehler-Treffern
   besteht. Bewusst leiser als die Werbekennzeichnung daneben: KEINE Fläche,
   nur gedämpfte Schrift. Der Hinweis erklärt ein Ergebnis, er fordert nichts
   und ist keine Pflichtangabe — bekäme er eine eigene Fläche, stünden über
   der Liste zwei gleich schwere Bänder, und das Pflichtband verlöre. */
.such-hinweis {
  display: flex;
  align-items: flex-start;
  gap: var(--abstand-s);
  margin-bottom: var(--abstand-m);
  font-size: var(--body-small);
  color: var(--on-surface-variant);
  line-height: var(--zeile-normal);
}

.such-hinweis svg {
  width: 16px; height: 16px;
  flex: 0 0 auto;
  margin-top: var(--abstand-xxs);
  color: var(--primary);
}

.rechtstext-zweck {
  margin-top: var(--abstand-xs);
  font-size: var(--body-small);
  color: var(--on-surface-variant);
  /* Auch der Zweck ist Fließtext und bekommt seine Grenze — bei 13px sind
     das 476,5px. Ohne sie lief er auf die volle Zeilenbreite mit. */
  max-width: var(--lesebreite);
}

/* ---------- Textseiten: Impressum, Datenschutz, Affiliate, Über ----------

   ⛔ EIGENER CONTAINERNAME, und das ist der Kern dieses Abschnitts. Bis zum
   11.08.2026 hieß er `.ueber-seite` und stand in SECHS Selektorlisten
   gemeinsam mit `.einstellungs-*`. Jede Layoutänderung an den Textseiten
   hätte /einstellungen mitgezogen — ohne Fehlermeldung, weil beide Seiten
   dieselben Regeln lesen. `.textseite` trennt das: Was hier steht, gilt für
   die vier Textseiten und für sonst nichts.

   ⚠️ Die KARTEN heißen weiterhin `.detail-karte.ueber-karte`. Das ist
   Absicht: `.ueber-karte` trägt nur Typografie (h2-Größe, Absatzbreite,
   p+p-Abstand), die für beide Seiten richtig ist — sie anzufassen gäbe es
   keinen Grund. Und `.detail-karte` wird von den Produktdetailseiten
   mitbenutzt (js/seite-detail.js, js/preisverlauf.js), steht in der
   --karten-kante-Liste und in der Druckregel; eine geänderte Kartenfläche
   schlüge dort durch. Beide bleiben unangetastet. */
.textseite {
  display: flex;
  flex-direction: column;
  /* Jede Karte ist ein Abschnitt für sich — derselbe Abstand wie zwischen
     Einleitung und erstem Abschnitt, damit die Seite EINEN Takt hat. */
  gap: var(--abstand-2xl);
  margin-top: var(--abstand-2xl);
  /* ⚠️ KEINE eigene max-width. Sie war der Grund für den Sprung bei 900px:
     Darunter band `.ueber-seite { max-width: 720px }`, darüber die Regel an
     <main> — und weil die 720px die größere Zahl waren, wurde die Spalte beim
     VERBREITERN des Fensters schmaler (gemessen 720 → 586,5px). Jetzt gibt
     die Breite allein <main> vor (css/layout.css, 1120px), und die Karten
     füllen sie. Damit haben Überschrift, Einleitung und alle Karten
     zwangsläufig dieselbe linke und rechte Kante.
     Das `margin-inline: auto` steht trotzdem da: Sollte hier je wieder eine
     Grenze eingezogen werden, ist sie von vornherein zentriert statt
     linksbündig — genau der Fehler, der oben entstanden wäre. */
  margin-inline: auto;
  width: 100%;
}

/* ---------- Druckerübersicht (/drucker) ----------

   GERÜST WIE FILAMENT UND RESINE (07.08.2026). Die Seite benutzt .liste-layout,
   .filter-sidebar und .listen-kopf der Produktlisten — dieselbe Breite,
   dieselbe ein- und ausklappbare Filterspalte, dieselbe klebende
   Werkzeugzeile. Hier stehen nur noch die Teile, die es dort nicht gibt.

   ⚠️ .drucker-seite ist ENTFALLEN. Der Kasten trug `max-width: 1120px` und
   machte die Seite damit 480px schmaler als die beiden Listen daneben, die
   die vollen 1600px von `main` nutzen — der auffälligste Unterschied
   überhaupt. Die Breite kommt jetzt von `main`, wie bei Filament und Resine.

   BREITE ZEILEN STATT KACHELN — das bleibt. Eine Kachel ist auf ein
   dominantes Element hin gebaut, bei Filament den Kilopreis. Ein Gerät hat
   keinen solchen einzelnen Wert, sondern ein Profil aus vier, fünf Angaben,
   die erst zusammen etwas sagen. Die Zeile gibt ihnen nebeneinander Platz. */

/* Der Einordnungssatz über der Liste. Er steht unter der Werkzeugzeile, weil
   er die Liste beschreibt und nicht das Werkzeug — und weil die klebende
   Zeile darüber sonst einen Absatz mitschleppte, den man einmal liest. */
.dr-einleitung {
  max-width: var(--lesebreite);
  font-size: var(--body-large);
  line-height: var(--zeile-text);
  color: var(--on-surface-variant);
}

/* Bauartwahl: Filament- gegen Harzdrucker. Zwei Bauarten, die kein einziges
   Feld teilen — deshalb eine Wahl und keine gemischte Liste.

   ⛔ SEIT DEM 18.09.2026 STEHT SIE IN DER ZEILE, nicht mehr als eigene
   Reihe (`flex: 1 0 100%`). Die Werkzeugzeile ist auf allen drei Listen
   dieselbe Abfolge — Ansichtswahl · Trefferzahl · Filter · Sortierung —, und
   die Bauartwahl ist das, was nur diese Liste zusätzlich hat: Sie tritt
   hinten an und bricht erst um, wenn der Platz nicht reicht. Eine erzwungene
   eigene Reihe machte aus der einen Zeile auf jeder Breite zwei. */
.dr-bauartwahl {
  display: flex;
  align-items: center;
  gap: var(--abstand-s);
  flex: 0 1 auto;
  /* ⚠️ UMBRECHEN ERLAUBT. Ohne dies stand die Zeile starr: Bei 320px
     Fensterbreite — die schmalste Größe, mit der ernsthaft zu rechnen ist —
     ragte sie gemessen 49px aus dem Bild, und ihr (i)-Knopf lag vollständig
     außerhalb. Erreichbar war er dort nicht, weil die overflow-x-Regel am
     html-Element den Rollbalken abfängt, der sonst entstanden wäre: Der Knopf
     existierte, war sichtbar nicht da und ließ sich nicht antippen.

     Umbrechen statt Schrumpfen der Knöpfe: Die beiden Bauarten tragen ihre
     Gerätezahl mit („FDM 148"), und ein geschrumpfter Knopf schnitte genau
     die ab. */
  flex-wrap: wrap;
  /* Bezugsrahmen für die Erklärblase des (i), siehe unten. */
  position: relative;
}

/* ⚠️ Die Blase des (i) hängt hier an der ZEILE, nicht am Symbol.
   `.krit-info::after` wächst sonst ab der linken Kante des Symbols nach
   rechts (left: 0, bis 260px breit). In der Filterspalte ist das richtig —
   dort steht das (i) links und die Spalte schneidet ohnehin zu. Hier steht es
   HINTER den beiden Bauart-Knöpfen, also bei rund 290px; gemessen bei 512px
   Fensterbreite ergab das ein 588px breites Dokument und damit einen
   waagerechten Rollbalken über die ganze Seite. Der Überhang nach rechts
   zählt zum rollbaren Bereich, und die Blase ist unsichtbar — der Balken
   erschien also ohne erkennbare Ursache.

   `position: static` am Symbol schiebt den Bezugsrahmen auf die Zeile: Die
   Blase beginnt an deren linker Kante und bleibt damit auf jeder Breite
   innerhalb des Inhalts. Ein `right: 0` hätte sie stattdessen nach links aus
   dem Inhalt geschoben — dort entsteht zwar kein Rollbalken, aber sie wäre
   abgeschnitten. */
.dr-bauartwahl .krit-info { position: static; }
/* max-width zusätzlich auf die Zeilenbreite gedeckelt: Die Grundregel erlaubt
   der Blase 260px, und auf einem 320px breiten Fenster bleibt nach den
   Seitenrändern weniger übrig. Beide Grenzen gelten, es zählt die kleinere. */
.dr-bauartwahl .krit-info::after {
  left: 0;
  top: calc(100% + 4px);
  max-width: min(260px, 100%);
}
.dr-bauart {
  display: inline-flex;
  align-items: center;
  gap: var(--abstand-s);
  padding: var(--abstand-s) var(--abstand-l);
  border: 1px solid var(--outline);
  border-radius: var(--radius-pille);
  background: none;
  color: var(--on-surface-variant);
  font-size: var(--label-large);
  font-weight: 500;
  cursor: pointer;
}
.dr-bauart:hover { background: var(--zustand-hover); }
.dr-bauart--aktiv {
  background: var(--secondary-container);
  color: var(--on-secondary-container);
  border-color: transparent;
}
.dr-bauart-zahl { font-size: var(--label-small); opacity: 0.8; }

/* Nennt den Strukturwechsel, den die Sortierwahl auslöst — sonst liest sich
   die verschwundene Gruppierung wie ein Fehler. Steht mit dem
   Einordnungssatz zusammen unter der Werkzeugzeile.

   ⚠️ Die frühere .dr-sortierzeile mit ihrem nackten <select> ist entfallen:
   Sortiert wird jetzt über denselben umrandeten Knopf mit Sheet wie bei
   Filament und Resine, und der steht in der klebenden .listen-kopf-Zeile.
   Ebenso .dr-trefferzahl — die Zahl trägt jetzt .listen-treffer. */
.dr-vorspann {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-xs);
  margin-bottom: var(--abstand-xl);
}
.dr-strukturhinweis { font-size: var(--body-small); color: var(--on-surface-variant); }

.dr-gruppe { margin-top: var(--abstand-2xl); }
.dr-gruppe-titel {
  display: flex;
  align-items: baseline;
  gap: var(--abstand-s);
  font-size: var(--title-large);
  font-weight: 500;
  padding-bottom: var(--abstand-s);
  border-bottom: 1px solid var(--outline-variant);
}
.dr-gruppe-zahl { font-size: var(--label-medium); color: var(--on-surface-variant); }

/* ---------- Die Gerätekarten (Bildvorlage „12c", 18.09.2026) ----------

   ⛔ EIN RASTER AUS KARTEN, KEINE FOLGE VON ZEILEN. Bis zum 18.09.2026 war die
   Gerätezeile am Desktop ein vierspaltiges Raster und wurde erst unter 780px
   Spaltenbreite zur Karte — die richtige Form, solange es nur diese eine
   Darstellung gab. Das dichte Zeilenlesen leistet seit dem 17.09.2026 die
   Tabellenansicht; zwei Darstellungen, die beide Zeilen zeigen, wären eine zu
   viel. Damit ist auch die Containerabfrage bei 779px entfallen: Es gibt
   keinen zweiten Aufbau mehr, zu dem sie umschalten könnte.

   ⚠️ `auto-fit` mit 300px Mindestbreite, keine feste Spaltenzahl: Auf der
   breiten Liste stehen drei Karten nebeneinander, mit ausgeklappter
   Filterspalte zwei, auf dem Telefon eine — von selbst, ohne eine zweite
   Medienabfrage, die man beim nächsten Umbau übersieht.

   ⛔ `min(300px, 100%)` UND NICHT NUR `300px`. Eine Mindestbreite ist eine
   Untergrenze, auch wenn der Platz kleiner ist: Bei 320px Fenster bleiben der
   Liste 273px, die Spur blieb aber 300px breit — gemessen 11px waagerechter
   Überlauf des ganzen Dokuments, verdeckt von `overflow-x: clip` am <html>,
   also lautlos. Mit `min(…, 100%)` gibt die Spur unterhalb ihrer
   Mindestbreite nach, statt das Dokument aufzudrücken. */
.dr-liste {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  gap: var(--abstand-m);
  margin-top: var(--abstand-l);
}

/* ⛔ FLEX-SPALTE, und das ist tragend: Nur so kann der Fuß mit `margin-top:
   auto` an der Unterkante kleben. Karten einer Rasterreihe sind gleich hoch;
   ohne den Fuß am Boden stünden die Balken benachbarter Karten auf
   verschiedenen Höhen, je nachdem wie lang der Gerätename umbricht.

   ⚠️ `container-type: inline-size` macht die KARTE zum Bezug ihrer eigenen
   Abfragen — nicht die Liste. Wie breit eine Karte ist, hängt an der
   Spaltenzahl des Rasters und nicht an der Fensterbreite; eine Medienabfrage
   träfe die Umbruchstelle darin nicht. */
.dr-zeile {
  /* Bezug der Klickfläche — siehe .dr-hauptbereich::after. */
  position: relative;
  container-type: inline-size;
  container-name: dr-karte;
  display: flex;
  flex-direction: column;
  /* Enger als bis zum 18.09.2026 (Polster --abstand-l, Lücke --abstand-m):
     Die Vorlage setzt die Blöcke dichter, und mit einzeiligem Fuß und
     einzeiligen Datenzeilen trägt die Karte ihren Inhalt in deutlich weniger
     Höhe — großzügige Abstände ließen sie dann leer wirken. */
  gap: var(--abstand-s);
  background: var(--surface-container);
  border-radius: var(--radius-l);
  box-shadow: var(--karten-kante);
  padding: var(--abstand-m);
}

/* Die Kopfzeile: Bild und Name links, die beiden Schalter rechts.
   ⚠️ `align-items: flex-start` — die Schalterspalte ist höher als das Bild,
   und beide sollen oben bündig stehen statt mittig zueinander. */
.dr-kopf {
  display: flex;
  align-items: flex-start;
  gap: var(--abstand-m);
}

.dr-hauptbereich {
  display: flex;
  align-items: flex-start;
  gap: var(--abstand-m);
  flex: 1 1 auto;
  min-width: 0;
  text-decoration: none;
  color: inherit;
}

/* ⛔ DIE GANZE KARTE IST DIE KLICKFLÄCHE.

   Materialfeld, Datenblock und Balken stehen außerhalb des Verweises — der
   Balken muss es (er trägt einen Knopf, und ein Knopf im Link ist ungültiges
   Markup), und was danach kommt, kann nicht im Link stehen. Ohne
   Gegenmaßnahme wäre die anklickbare Fläche damit auf Bild und Name
   geschrumpft. Dieselbe Technik wie bei .ok-hauptlink der Merkzettel-Ordner.

   ⛔ Alles, was selbst anklickbar ist, MUSS darüber liegen — sonst fängt die
   Fläche seine Klicks ab: der Balkenknopf mit seiner Erklärung und die beiden
   Schalter. */
.dr-hauptbereich::after { content: ""; position: absolute; inset: 0; }
.dr-balken-block,
.dr-aktionen { position: relative; z-index: 1; }

/* ⛔ DER AUFGEKLAPPTE BALKENBLOCK LIEGT ÜBER SEINEN NACHBARKARTEN (20.09.2026).

   `.dr-erklaerung` öffnet nach unten aus der Karte heraus und deckt damit die
   Karte darunter ab. `.dr-zeile` trägt `position: relative` OHNE z-index, ist
   also KEIN Stapelkontext — der Block (1) und die Schalter (1) jeder Karte
   stehen deshalb alle im selben Kontext. Bei Gleichstand entscheidet die
   Reihenfolge im Markup, und die nächste Karte kommt später: Sie gewinnt.

   Gemessen an der laufenden Seite (1474px, erste Karte aufgeklappt): Von den
   Punkten über der 320×235px großen Erklärung lagen 44 auf dem Herz der
   nächsten Karte, 44 auf ihrem Vergleichsknopf und 53 auf ihrem eigenen
   Balkenblock — durchgeschienen ist also mehr als die beiden gemeldeten
   Symbole.

   ⚠️ Es war NIE eine Frage der Deckkraft: Die Erklärung steht auf
   `--surface-container-highest`, gemessen `rgb(44, 49, 56)` und damit voll
   deckend. Wer hier eine Fläche nachträgt, behebt nichts.

   ⛔ GEHOBEN WIRD NUR, SOLANGE SIE ZU SEHEN IST — und genau an den drei
   Zuständen, die sie zeigen (siehe die Regeln bei `.dr-erklaerung`). Ein
   dauerhaftes `z-index: 2` höbe JEDEN Balkenblock über die Schalter der
   nächsten Karte, also auch die 47, die gerade nichts zeigen.

   ⚠️ NICHT an der KARTE (`.dr-zeile:has(...) { z-index: 2 }`). Der Weg ist
   verlockend — er höbe die ganze Karte samt allem darin — und macht die
   Karte dabei zum Stapelkontext. Stehen zwei Erklärungen offen, deckte die
   spätere Karte die frühere Erklärung dann mit ihrem eigenen GRUND ab; heute
   liegt der Grund als Blockhintergrund unter jedem positionierten Element und
   kann das nicht. Der Block hebt nur sich selbst, und die Schalter der
   Nachbarkarte bleiben darunter. */
.dr-balken-block:hover,
.dr-balken-block:focus-within,
.dr-balken-block--offen { z-index: 2; }

/* Das Vorschaubild, 46px wie in der Vorlage. Der Textverweis darunter ist am
   18.09.2026 entfallen — er steht unverändert auf dem Datenblatt, das diese
   Karte öffnet (Begründung bei kartenBildHtml in js/seite-drucker.js). */
.dr-bild {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  border-radius: var(--radius-s);
  background: var(--surface-container-highest);
}
.dr-bild img { width: 46px; height: 46px; opacity: 0.55; }
/* Die Karte eines Harzdruckers: dieselbe Entscheidung wie auf dem Datenblatt,
   nur ohne Satz — auf 46px hätte keiner Platz. Der gestrichelte Rahmen trägt
   hier die ganze Aussage, und er nimmt `--outline`: Er IST die Aussage dieser
   Fläche und braucht die 3:1 eines grafischen Objekts. */
.dr-bild--leer {
  background: none;
  border: 1px dashed var(--outline);
  color: var(--on-surface-variant);
}
.dr-bild--leer svg { width: 20px; height: 20px; }

.dr-inhalt { display: block; min-width: 0; }

/* ⛔ DER NAME STEHT IN DER SERIF — wie jede Überschrift der Seite, und wie in
   der Bildvorlage. Der Hersteller tritt dabei zurück, das Modell trägt das
   Gewicht: Wer eine Liste von Creality-Geräten durchgeht, liest das Modell. */
/* ⛔ SCHRIFT UND GRÖSSE DER PRODUKTKACHEL (23.09.2026): Der Name steht wie
   .kachel-produktname in Serif 18px/600. Er bricht hier weiterhin um statt
   zu kürzen — die Schalter stehen in derselben Zeile. */
.dr-name {
  display: block;
  font-family: var(--schrift-serif);
  font-size: var(--title-medium);
  font-weight: 600;
  line-height: var(--zeile-eng);
}
.dr-hersteller { color: var(--on-surface-variant); }
.dr-modell { font-weight: 500; }
.dr-name .dr-modell { font-weight: inherit; }
/* „nicht mehr erhältlich" als WORT, in derselben Schrift wie „aktuell" —
   keine Ausgrauung. Diese Geräte sind der Grund, warum es die Ansicht gibt.
   ⚠️ Jahr und Marktstand sind abgelesene Angaben und stehen deshalb in der
   Schreibmaschinenschrift — dieselbe Regel wie überall im Projekt.
   14px wie die Mengenzeile der Produktkachel (23.09.2026). */
.dr-stand {
  display: block;
  margin-top: var(--abstand-xs);
  font-family: var(--schrift-mono);
  font-size: var(--body-medium);
  color: var(--on-surface-variant);
}
.dr-stand-wort { font-weight: 500; }

/* ⛔ DIE SCHALTER STEHEN NEBENEINANDER (Bildvorlage, 18.09.2026 abends).

   Sie standen bis dahin übereinander, mit der Begründung, nebeneinander
   drückten sie den Namen auf die halbe Breite. Das stimmt und ist der Preis:
   Gemessen bleiben dem Namen auf der schmalsten Karte (300px) noch 106px,
   er bricht dort also nach dem Herstellernamen um. Dafür ist die Kopfzeile
   nur noch so hoch wie EIN Schalter statt wie zwei.

   ⛔ 44 × 44px AUF JEDER BREITE, nicht mehr 32px am Desktop. Die alte Zahl
   hatte einen Grund, den das Nebeneinander aufhebt: Zwei gestapelte 44er
   wären 92px hoch gewesen und damit höher als die Kopfzeile daneben — die
   Karte wäre um eine Zeile gewachsen, für zwei Symbole. Nebeneinander kosten
   sie in der Höhe gar nichts (die Kopfzeile ist über das 46px-Bild ohnehin
   höher), und die Trefferfläche aus WCAG 2.5.5 gilt dann überall gleich,
   statt an einer Breitenschwelle zu hängen. */
.dr-aktionen {
  display: flex;
  gap: var(--abstand-xs);
  flex: 0 0 auto;
}
.dr-aktionen .herz-knopf,
.dr-aktionen .vergleich-knopf { width: 44px; height: 44px; }

/* ⛔ DIE MATERIALZEILE IST EIN EIGENES FELD (Bildvorlage „12c").

   Sie ist der Kern dieser Ansicht — sie beantwortet die Frage, mit der jemand
   herkommt. Als Textzeile stand sie zwischen Name und Zahlen und war von
   beiden nicht zu unterscheiden; als Feld mit eigener Fläche ist sie das, was
   man beim Überfliegen zuerst sieht.

   ⚠️ Die Fläche ist sehr dunkel und die Kante trägt die Abgrenzung — dieselbe
   Bauart wie bei den Karten der Seite (Kante statt Fläche). */
.dr-material {
  display: flex;
  align-items: center;
  gap: var(--abstand-s);
  margin: 0;
  padding: var(--abstand-s) var(--abstand-m);
  border: 1px solid var(--gruen-kante);
  border-radius: var(--radius-s);
  background: var(--gruen-feld);
}
.dr-material-zahl {
  flex: 0 0 auto;
  font-family: var(--schrift-mono);
  font-size: var(--title-small);
  font-weight: 500;
  color: var(--balken-gruen);
}
.dr-material-wort {
  font-size: var(--body-small);
  line-height: var(--zeile-eng);
  color: var(--on-surface-variant);
}
/* Nicht ermittelbar: kein Feld, keine Zahl, kein Grün. Weder 0 noch 33 wären
   wahr, und eine grüne Fläche über einer Lücke wäre eine Zusage. */
.dr-material--offen {
  display: block;
  padding: 0;
  border: 0;
  background: none;
  font-size: var(--body-medium);
  font-style: italic;
  color: var(--on-surface-variant);
}

/* ---------- Die technischen Angaben: vier Zeilen ----------

   ⛔ BESCHRIFTUNG LINKS, WERT RECHTSBÜNDIG — eine Zeile je Angabe
   (Bildvorlage, 18.09.2026 abends).

   Bis dahin stand die Beschriftung ÜBER dem Wert und die vier Felder in zwei
   Spalten. Diese Form war die Antwort auf zwei Felder NEBENEINANDER, für die
   „Gehäuse" und „geschlossen, beheizt" gemessen 230px in einer Zeile
   brauchten — auf einer halben 300px-Karte also nie genug. Über die VOLLE
   Kartenbreite stellt sich die Frage nicht mehr, und vier einzeilige Zeilen
   sind flacher als zwei Doppelreihen.

   ⛔ FLEX MIT `justify-content: space-between`, kein zweispaltiges Raster mit
   fester Beschriftungsspalte. Eine feste Spur (etwa `90px 1fr`) müsste auf
   das längste Wort gerechnet sein — „EXTRUDER" — und ließe bei allen anderen
   Zeilen Luft stehen, die dem Wert fehlt. So nimmt jede Zeile für sich genau
   so viel, wie sie braucht.

   ⚠️ DIE WERTE FLUCHTEN TROTZDEM RECHTS, weil alle an derselben Kante enden.
   Untereinander bündig sind damit die Wert-ENDEN, nicht ihre Anfänge — und
   genau das zeigt die Vorlage. */
.dr-daten {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-xs);
  margin: 0;
}
/* ⚠️ `align-items: baseline` und nicht `center`: Beschriftung und Wert sind
   verschieden groß (13 gegen 14px, seit dem 23.09.2026). Auf der Mitte ausgerichtet säße die
   kleinere Beschriftung sichtbar zu hoch; auf der Grundlinie stehen beide
   auf derselben Linie wie in der Vorlage.

   ⚠️ `gap` ist Pflicht und nicht bloß Zierde: Er ist der Mindestabstand
   zwischen Beschriftung und Wert, wenn beide zusammen die Zeile füllen —
   ohne ihn stießen „GEHÄUSE" und „geschlossen, beheizt" auf der schmalsten
   Karte unmittelbar aneinander. */
.dr-datenzeile {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--abstand-m);
  min-width: 0;
}
.dr-datenlabel {
  /* ⛔ Sie gibt NICHT nach. Der Wert ist die Angabe, um die es geht, und er
     darf umbrechen; eine umbrechende BESCHRIFTUNG („GEHÄU-SE") liest sich als
     Fehler. Sie ist mit höchstens acht Zeichen ohnehin kurz. */
  flex: 0 0 auto;
  font-family: var(--schrift-mono);
  /* 13px wie die Schrift des Materialkennzeichens (23.09.2026). */
  font-size: var(--body-small);
  line-height: var(--zeile-eng);
  letter-spacing: 0.1em;
  /* Die Großschreibung entsteht hier, im Markup steht gemischter Text —
     Vorlesehilfen buchstabieren durchgehende Versalien sonst teilweise. */
  text-transform: uppercase;
  /* ⛔ SIE STEHT DUNKLER ALS IHR WERT (18.09.2026).

     Der Wert ist die Information, die Bezeichnung ordnet sie nur ein. Beide
     standen vorher eine Stufe auseinander (--on-surface-variant gegen
     --on-surface, gemessen 10,15 gegen 13,78:1 auf der Karte) — zu wenig, um
     die Zeile beim Überfliegen von links nach rechts zu lesen.

     ⚠️ --outline ist im Projekt als LINIENROLLE dokumentiert (sie begrenzt
     Bedienelemente und trägt dafür ihre 3:1). Sie steht hier trotzdem, weil
     sie die einzige vorhandene Rolle zwischen --on-surface-variant und der
     Haarlinie ist: --outline-variant trägt gegen die Karte 1,73:1 und wäre
     unlesbar. Ein eigener Grauwert wäre eine zweite Fassung derselben Stufe.
     GEMESSEN 5,50:1 gegen die Kartenfläche, also über den 4,5:1 aus WCAG
     1.4.3 — die Bezeichnung bleibt lesbar.

     ⛔ WER --outline ÄNDERT, ÄNDERT DIESEN TEXT MIT. Die Rolle ist dort auf
     3:1 gerechnet; sie hier zu lesen hängt an einer Zahl, die für Kanten
     gewählt wurde. Wer sie zurücknimmt, misst diese Paarung nach. */
  color: var(--outline);
}
.dr-datenwert {
  /* `min-width: 0` ist Pflicht, sonst wird ein Flex-Kind nie schmaler als
     sein längstes unteilbares Wort und drückt die Zeile über die Karte
     hinaus — derselbe Fall wie bei .ordner-karte und .sortier-option. */
  margin: 0;
  min-width: 0;
  text-align: right;
  font-size: var(--body-medium);
  line-height: var(--zeile-normal);
  color: var(--on-surface);
  font-variant-numeric: tabular-nums;
}
/* ⛔ AUSNAHME VON „GEMESSEN → MONO" (23.09.2026, entschieden): Alle Werte der
   Datenzeilen stehen in der Grotesk 14px — Schrift und Größe des Farbnamens
   der Produktkachel —, auch die abgelesenen (Bauraum, Düse/Bett). Die
   Ziffernflucht trägt `tabular-nums` an .dr-datenwert, wie bei .ks-wert.
   Die Klasse --mess bleibt am Markup (datenPaare führt das Feld weiter);
   sie hat hier nichts mehr zu tun. */
/* ⚠️ Ein zusammengesetzter Wert bricht nur an seinem Trenner um, nicht
   zwischen Zahl und Einheit. Ohne das stand „bis 300 °C / 120" in der ersten
   und „°C" allein in der zweiten Zeile — eine Einheit ohne ihre Zahl. */
.dr-wert-teil { white-space: nowrap; }
/* „nicht hinterlegt" ist ein fehlender Wert, kein Wert — er steht deshalb in
   der Schrift der übrigen Beschriftungen und nicht im Gewicht einer Angabe. */
.dr-datenwert--fehlt {
  color: var(--on-surface-variant);
  font-style: italic;
}

/* ---------- Der Fuß: Beschriftung, Wert, Balken ----------

   ⛔ `margin-top: auto` schiebt ihn an die Unterkante der Karte. Karten einer
   Rasterreihe sind gleich hoch; ohne das stünden die Balken benachbarter
   Karten auf verschiedenen Höhen, je nachdem wie lang darüber der Name
   umbricht — und genau nebeneinander liest man sie.

   ⚠️ Er bleibt ein KNOPF (siehe balkenHtml in js/seite-drucker.js): In Ruhe
   sieht er aus wie in der Bildvorlage, er trägt aber den Weg zur Herleitung
   samt dem Satz „kein Urteil über das Gerät". */
.dr-balken-block {
  margin-top: auto;
  padding-top: var(--abstand-s);
  border-top: 1px solid var(--outline-variant);
}
.dr-balken-knopf {
  display: block;
  width: 100%;
  border: 0;
  background: none;
  padding: 0;
  cursor: pointer;
  text-align: left;
  color: inherit;
}
/* ⚠️ Der Hover liegt auf dem BLOCK und nicht auf dem Knopf: Der Knopf hat
   keinen eigenen Innenabstand mehr, eine Fläche darauf endete an den
   Buchstaben. */
.dr-balken-block:hover { border-top-color: var(--outline); }
/* ⛔ EINE ZEILE: Beschriftung, Balken, Wert (Bildvorlage, 18.09.2026 abends).

   Der Balken lag bis dahin UNTER dieser Zeile und kostete die Karte damit
   eine eigene Zeile Höhe. Jetzt ist er das mittlere Kind und nimmt den Platz,
   den Beschriftung und Wert übriglassen (`flex: 1 1 auto` an .dr-balken).

   ⚠️ `align-items: center` und nicht `baseline` wie bei den Datenzeilen: Der
   Balken hat keine Grundlinie — an einer ausgerichtet hinge er unten am Text
   statt auf seiner Höhe zu stehen. */
.dr-balken-kopf {
  display: flex;
  align-items: center;
  gap: var(--abstand-s);
}
.dr-balken-titel {
  flex: 0 0 auto;
  font-family: var(--schrift-mono);
  font-size: var(--body-small);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--on-surface-variant);
}
/* ⛔ DER WERT STEHT IN DER SERIF — wie der Preis auf der Produktkachel
   (18.09.2026).

   Er ist die dominante Kennzahl dieser Karte, und die Serif trägt im ganzen
   Projekt genau das: Überschriften, Preise, Kennzahlen (Tabelle der drei
   Familien in css/theme.css). Die Produktkachel setzt ihren Preis so
   (.preis-pro-kg), und das Datenblatt setzt DIESELBE Prozentzahl ebenfalls so
   (.dd-bandbreite-zahl strong) — auf der Karte stand sie als einzige Stelle
   in der Grotesk.

   ⚠️ Der frühere Kommentar begründete die Grotesk damit, die Zahl sei eine
   GERECHNETE Kennzahl und kein abgelesener Messwert. Das bleibt richtig und
   ist der Grund, warum sie nicht in der Schreibmaschinenschrift steht — es
   ist aber kein Grund für die Grotesk: Kennzahlen trägt die Serif.

   ⚠️ Größe, Gewicht und Farbe sind unverändert. Die Zahl soll nicht lauter
   werden, sie soll in derselben Familie stehen wie ihre Entsprechung. */
.dr-balken-wert {
  /* Er gibt so wenig nach wie die Beschriftung — er IST die Auskunft der
     Zeile; nachgeben soll der Balken dazwischen. */
  flex: 0 0 auto;
  font-family: var(--schrift-serif);
  font-size: var(--title-small);
  font-weight: 500;
  color: var(--primary);
}
.dr-balken-offen {
  margin-top: auto;
  padding-top: var(--abstand-s);
  border-top: 1px solid var(--outline-variant);
  font-size: var(--label-small);
  font-style: italic;
  color: var(--on-surface-variant);
}

/* Erklärung bei Hover, Tastaturfokus UND Antippen. Die ersten beiden macht
   CSS, das Antippen ein Umschalter in js/seite-drucker.js — ein
   title-Attribut erschiene auf Touch-Geräten nie. */
.dr-erklaerung {
  position: absolute;
  right: 0;
  top: 100%;
  z-index: 30;
  width: 320px;
  max-width: 80vw;
  margin-top: var(--abstand-xs);
  padding: var(--abstand-m);
  background: var(--surface-container-highest);
  border: 1px solid var(--outline-variant);
  border-radius: var(--radius-m);
  box-shadow: var(--schatten-menue, 0 8px 24px rgba(0,0,0,.28));
  font-size: var(--body-small);
}
.dr-balken-block:hover .dr-erklaerung,
.dr-balken-block:focus-within .dr-erklaerung,
.dr-balken-block--offen .dr-erklaerung { display: block; }
/* hidden wird vom Umschalter gesetzt; Hover und Fokus heben es hier auf. */
.dr-balken-block:hover .dr-erklaerung[hidden],
.dr-balken-block:focus-within .dr-erklaerung[hidden] { display: block; }
.dr-erklaerung-lesart {
  color: var(--on-surface-variant);
  margin-bottom: var(--abstand-s);
  padding-bottom: var(--abstand-s);
  border-bottom: 1px solid var(--outline-variant);
}
.dr-erklaerung ul { list-style: none; display: flex; flex-direction: column; gap: var(--abstand-xxs); }
.dr-punkt--ja { color: var(--balken-gruen); }
.dr-punkt--nein { color: var(--on-surface-variant); }
.dr-punkt--offen { color: var(--on-surface-variant); font-style: italic; }

/* ⛔ ER IST DAS DEHNENDE KIND der Fußzeile — und `min-width: 0` gehört dazu:
   Ohne sie könnte ein langer Wert („95.175 cm³") die Zeile über die Karte
   hinausdrücken, statt dass der Balken nachgibt.

   ⚠️ `margin-top` ist entfallen. Er stand darunter, jetzt steht er daneben;
   ein Abstand nach oben schöbe ihn gegen die Grundlinie seiner Nachbarn. */
.dr-balken {
  display: block;
  flex: 1 1 auto;
  min-width: 0;
  height: 6px;
  border-radius: 3px;
  background: var(--surface-variant);
  overflow: hidden;
}
.dr-balken-fuellung { display: block; height: 100%; background: var(--primary); border-radius: 3px; }

.dr-leer {
  margin-top: var(--abstand-xl);
  padding: var(--abstand-2xl);
  text-align: center;
  color: var(--on-surface-variant);
  background: var(--surface-container);
  border-radius: var(--radius-m);
}

/* ---------- Tippflächen: die Breitenschwelle ist ENTFALLEN ----------

   Hier stand bis zum 18.09.2026 eine `@media (max-width: 639px)`-Abfrage, die
   Herz und Vergleich auf dem Telefon von 32 auf 44px vergrößerte. Sie ist mit
   dem Nebeneinander gegenstandslos geworden: Beide Schalter sind jetzt auf
   JEDER Breite 44px, es gibt also keine Schwelle mehr zu überschreiten.

   ⚠️ IHRE BEGRÜNDUNG BLEIBT LEHRREICH und gehört zu jeder künftigen Abfrage
   an dieser Karte: Sie war bewusst eine MEDIENabfrage und keine
   CONTAINERabfrage, weil nicht gefragt war, wie breit die Karte ist, sondern
   ob ein DAUMEN darauf zielt. Beides fällt hier auseinander, und zwar
   gemessen: Bei 1600px Fenster stehen drei Karten von je 329px nebeneinander,
   bei 390px Fenster steht eine von 343px — die Karte ist auf dem Telefon also
   BREITER als am Desktop. Eine Containerabfrage hätte am Desktop die großen
   Knöpfe gezeigt und auf dem Telefon die kleinen, also genau verkehrt herum.

   ⚠️ Die Größe steht weiterhin an `.dr-aktionen .herz-knopf` und nicht an
   `.herz-knopf` — dieselbe Klasse trägt auch die Produktkacheln, deren
   Kopfzeile auf 36px gerechnet ist. */

/* ---------- Tabellenansicht der Geräte (17.09.2026) ----------

   Zweite Darstellung derselben Auswahl — wie auf /filamente und /resine, und
   über denselben gespeicherten Schalter (printsupport.listenansicht).

   Rahmen, Zellpolster, klebender Spaltenkopf und die Kante kommen aus
   `.angebots-tabelle` (css/komponenten.css): Es ist DIESELBE Tabelle, nicht
   eine zweite, die so aussieht. Eigen sind hier allein die Spalten.

   ⛔ DIE SPALTENKLASSEN HEISSEN ANDERS ALS DIE DER ANGEBOTSTABELLE
   (.sp-geraet statt .sp-produkt, .sp-drwege statt .sp-aktionen), und das ist
   kein Geschmack. Die Container-Stufen dort blenden `.sp-aktionen` unter 590px
   aus und geben `.sp-produkt` 220px Mindestbreite — beides gälte hier
   stillschweigend mit, obwohl diese Tabelle andere Spalten, andere Breiten und
   andere Schwellen hat. Gleiche Namen wären eine Kopplung, die man erst
   bemerkt, wenn drüben jemand eine Schwelle verschiebt.

   ⚠️ Was die Tabelle NICHT trägt: Volumen und Extruder (die Karte führt sie in
   ihrer Datenliste), den Verweis auf die Herstellerseite und die aufklappbare
   Herleitung des Balkens. Alle drei stehen auf der Karte und auf dem
   Datenblatt; die Begründung für den Balken steht bei tabellenBalkenHtml in
   js/seite-drucker.js. */

/* ⚠️ Die Untergrenze aus `.angebots-tabelle` (280px) ist die der
   ANGEBOTStabelle und hier eine Nummer zu groß: In ihrer kleinsten Stufe
   braucht diese Tabelle 240px. Mit 280 stünde sie auf einem 320px-Telefon
   breiter da, als ihr Inhalt verlangt, und rollte deshalb — die Untergrenze
   erzeugte selbst den Anschnitt, den sie verhindern soll. */
.drucker-tabelle { min-width: 240px; }

.drucker-tabelle .sp-geraet { min-width: 220px; width: 100%; }

/* ⚠️ Der Spaltenkopf der Bandbreite darf umbrechen — als einziger. Der
   Kopf steht sonst auf `nowrap` (eine Zahlenspalte ohne Überschrift ist beim
   Rollen nicht mehr zuzuordnen), und „MATERIALBANDBREITE" belädt die Spalte
   damit gemessen mit 156px, während der Balken darunter mit 90 auskommt. Das
   Wort zu kürzen wäre die falsche Ersparnis: „Bandbreite" allein sagt nicht,
   wovon. */
.drucker-tabelle thead .sp-bandbreite { white-space: normal; }

/* Trenner zwischen den Spalten — dieselbe Bauart und dieselbe Begründung wie
   bei `.produkt-tabelle`: `> * + *`, weil die erste Zelle jeder Zeile ein
   <th scope="row"> ist und weil ein `display: none` dazwischen die Linie
   stillschweigend auf die nächste sichtbare Spalte verschieben soll, statt
   sie an die Außenkante zu setzen. */
.drucker-tabelle tr > * + * { border-left: 1px solid var(--outline-variant); }

/* Alle Wertspalten mittig, Kopf wie Zelle — die Gerätespalte bleibt
   linksbündig: Sie trägt die ganze Restbreite und Namen sehr verschiedener
   Länge, und sie ist die Zeile, an der man die Liste entlangliest. */
.drucker-tabelle td,
.drucker-tabelle thead th + th { text-align: center; }

/* Abgelesene Werte in der Schreibmaschinenschrift — dieselbe Regel wie auf der
   Karte und in der Angebotstabelle: Was gemessen ist, sieht überall gleich aus.
   ⚠️ `nowrap` ist hier tragend, nicht kosmetisch: „256 × 256 × 260 mm" würde
   sonst an jedem Leerzeichen umbrechen, und die Zeilenhöhe der ganzen Tabelle
   hinge daran, welches Gerät die längste Angabe hat. */
.drucker-tabelle .sp-bauraum,
.drucker-tabelle .sp-duese,
.drucker-tabelle .sp-bett,
.drucker-tabelle .sp-aufloesung,
.drucker-tabelle .sp-lcd,
.drucker-tabelle .sp-wellenlaenge {
  font-family: var(--schrift-mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Die Gerätespalte: Bild und Name nebeneinander, Jahr und Marktstand darunter.
   `.tz-name` und `.tz-bild` kommen aus der Angebotstabelle. */
.dr-tab-geraet { align-items: center; }
.dr-tab-name { display: block; font-weight: 400; }
.dr-tab-stand {
  display: block;
  margin-top: 2px;
  font-family: var(--schrift-mono);
  font-size: var(--label-medium);
  font-weight: 400;
  color: var(--on-surface-variant);
}

/* Ohne hinterlegte Zeichnung eine gestrichelte Fläche statt der gefüllten —
   dieselbe Entscheidung wie auf Karte und Datenblatt, nur auf 40px. Die Kante
   IST hier die Aussage und nimmt deshalb `--outline`, nicht die Haarlinie. */
.dr-tab-bild--leer {
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px dashed var(--outline);
  color: var(--on-surface-variant);
}
.dr-tab-bild--leer svg { width: 18px; height: 18px; }

/* ⛔ DERSELBE CHIP WIE AUF DER KARTE (.dr-material) — nur enger. Er kommt aus
   demselben Baustein (materialChipHtml); hier steht er in einer Zelle und
   nicht in einer Karte, also nimmt er seinen Platz nach Inhalt statt über die
   volle Breite. Ein zweiter Chip mit eigenen Klassen wäre eine zweite
   Fassung derselben Aussage, und die falsche sähe man in der Ansicht, die man
   gerade nicht gewählt hat. */
.drucker-tabelle .dr-material {
  display: inline-flex;
  gap: var(--abstand-xs);
  padding: var(--abstand-xs) var(--abstand-s);
  text-align: left;
}
.drucker-tabelle .dr-material-zahl {
  font-size: var(--body-medium);
  white-space: nowrap;
}
.drucker-tabelle .dr-material-wort { font-size: var(--label-small); }
.drucker-tabelle .dr-material--offen {
  display: inline-block;
  font-size: var(--label-medium);
}

/* Der Balken und sein Wert in einer Zeile: links die Strecke, rechts die Zahl.
   ⚠️ Er braucht hier KEINE eigenen Angaben mehr: Seit die Karte ihren Fuß
   ebenfalls einzeilig setzt (18.09.2026), bringt `.dr-balken` `flex: 1 1 auto`
   und keinen oberen Abstand schon selbst mit. Die frühere Rücknahme stand
   hier, solange er in der Karte UNTER seiner Beschriftung lag. */
.dr-tab-balken {
  display: flex;
  align-items: center;
  gap: var(--abstand-s);
}
.dr-tab-balken-wert {
  flex: 0 0 auto;
  font-family: var(--schrift-mono);
  font-weight: 500;
  color: var(--primary);
}

.dr-tab-leer { color: var(--on-surface-variant); }

/* ⛔ min-width, NICHT width — wie bei der Angebotstabelle: `width` ist im
   automatischen Tabellenlayout ein Vorschlag, `min-width` eine Untergrenze.
   Zwei Symbolknöpfe à 36px, ihre Lücke und das Zellpolster. */
.drucker-tabelle .sp-drwege { min-width: 96px; }

/* ---------- Was bei wenig Platz zuerst geht ----------

   ⛔ DIE STUFEN STEHEN HINTER DEN GRUNDREGELN DER SPALTEN, UND DAS IST PFLICHT.
   Eine Containerabfrage erhöht die Spezifität NICHT — es entscheidet allein die
   Reihenfolge in der Datei. Die letzte Stufe setzt `min-width` an derselben
   Spalte wie `.sp-geraet` oben; stünde sie davor, gewänne die Grundregel und
   die Tabelle rollte auf dem Telefon weiter. Dieselbe Falle wie bei der
   Angebotstabelle und bei den Kategorien der Kopfleiste.

   ⛔ ZWEI LADDERN, EINE JE BAUART — nicht eine gemeinsame. Die Tabellen haben
   verschiedene Spalten und damit verschiedene Gesamtbreiten: Eine geteilte
   Schwelle bei 1000px nähme dem Harzdrucker seine LCD-Spalte, obwohl seine
   sieben Spalten dort längst nebeneinander passen. Der Container ist
   `.tabelle-rahmen`, also die Breite der LISTE — mit der 420px breiten
   Filterspalte daneben bleiben bei 1440px Fenster 852px, eingeklappt 1272px.
   Dieselbe Fensterbreite trägt also einmal alle Spalten und einmal nicht.

   ⛔ ABGEGEBEN WIRD NACH WICHTIGKEIT, NICHT NACH POSITION. Zuerst geht, was
   schon woanders steht oder am wenigsten unterscheidet: das Gehäuse (es geht
   ohnehin in die Materialbandbreite ein), dann Bett und Düse (zwei Zahlen, die
   dasselbe Merkmal von zwei Seiten beschreiben), dann der Bauraum. Die
   MATERIALSPALTE bleibt am längsten — sie ist die Aussage, für die es diese
   Liste gibt.

   ⚠️ `display: none` und nicht `visibility: hidden`: Eine unsichtbare Zelle
   belegte ihre Spaltenbreite weiter, und genau die soll frei werden.

   ⚠️ GEMESSEN WIRD DER INHALTSKASTEN, NICHT DIE ÄUSSERE BREITE. `.tabelle-rahmen`
   trägt 1px Polster (es hält seine Kante frei); eine Containerabfrage vergleicht
   gegen den Inhaltskasten, also gegen `clientWidth − 2`. Beim Bauen genau so
   zugeschnappt: Bei 1440px Fenster und ausgeklappter Filterspalte misst der
   Rahmen 852px, der Container aber 850 — mit der Schwelle bei 850px fiel die
   Düsenspalte, obwohl sie hineinpasste.

   Gemessen an der laufenden Seite, jede Stufe als schmalste Breite, in der sie
   noch vollständig steht (Chrome, `width: min-content` je Stufe):

     FDM    alle acht Spalten                    1000px → unter 1005 fällt Gehäuse
            ohne Gehäuse                          910px → unter  915 fällt Bett
            ohne Gehäuse, Bett                     847px → unter  846 fällt Düse
            ohne Gehäuse, Bett, Düse               750px → unter  754 fällt Bauraum
            Gerät, Materialien, Bandbreite, Wege   586px → unter  590 fällt Bandbreite
            Gerät, Materialien, Wege               430px → unter  434 fallen die Wege
            Gerät, Materialien                     333px → unter  400 schrumpft Gerät
            Gerät (schmal), Materialien            240px → paßt bis 320px Fenster

     RESIN  alle sieben Spalten                   892px → unter  897 fällt LCD
            ohne LCD                              837px → unter  842 fällt Wellenlänge
            ohne LCD, Wellenlänge                  737px → unter  742 fällt Auflösung
            Gerät, Bauraum, Bauvolumen, Wege       572px → unter  577 fällt Bauvolumen
            Gerät, Bauraum, Wege                   480px → unter  485 fallen die Wege
            Gerät, Bauraum                         384px → unter  400 schrumpft Gerät */

@container (max-width: 1004px) {
  .drucker-tabelle--fdm .sp-gehaeuse { display: none; }
}
@container (max-width: 914px) {
  .drucker-tabelle--fdm .sp-bett { display: none; }
}
@container (max-width: 845px) {
  .drucker-tabelle--fdm .sp-duese { display: none; }
}
@container (max-width: 753px) {
  .drucker-tabelle--fdm .sp-bauraum { display: none; }
}
@container (max-width: 589px) {
  .drucker-tabelle--fdm .sp-bandbreite { display: none; }
}
@container (max-width: 433px) {
  .drucker-tabelle--fdm .sp-drwege { display: none; }
}

@container (max-width: 896px) {
  .drucker-tabelle--resin .sp-lcd { display: none; }
}
@container (max-width: 841px) {
  .drucker-tabelle--resin .sp-wellenlaenge { display: none; }
}
@container (max-width: 741px) {
  .drucker-tabelle--resin .sp-aufloesung { display: none; }
}
@container (max-width: 576px) {
  .drucker-tabelle--resin .sp-bandbreite { display: none; }
}
@container (max-width: 484px) {
  .drucker-tabelle--resin .sp-drwege { display: none; }
}

/* Die letzte Stufe gilt beiden Bauarten: Das Vorschaubild geht als Erstes,
   nicht der Name — es wiederholt nur, was der Name sagt.
   ⚠️ Und der Bauraum darf hier umbrechen. Sein `nowrap` hält sonst 164px
   fest; zusammen mit einer 120px-Gerätespalte sind das 284px, und der Rahmen
   ist bei 320px Fenster 273px breit — die Harztabelle rollte dort um 11px. */
@container (max-width: 399px) {
  .drucker-tabelle .sp-geraet { min-width: 120px; }
  .drucker-tabelle .tz-bild { display: none; }
  .drucker-tabelle .sp-bauraum { white-space: normal; }
}

/* ---------- Filter der Druckerübersicht ----------

   ⚠️ HIER STAND EINE EIGENE FILTERLEISTE (.dr-layout, .dr-filter,
   .dr-filterblock, .dr-blockhinweis, .dr-chips, .dr-suchfeld, .dr-zahlpaar,
   .dr-zahlfeld, .dr-schalter, .dr-filterfuss). Alle diese Regeln sind am
   07.08.2026 ersatzlos entfallen: Der Filter benutzt jetzt .liste-layout und
   .filter-sidebar der Produktlisten, und damit deren Breite (420px),
   Einklappen, Klebeverhalten, Scrollfläche, Kriteriumsköpfe mit (i) und den
   klebenden Anwenden-Fuß.

   Die frühere Begründung („von den elf Kriterien der Produktfilter gilt hier
   keines") galt der LOGIK und gilt dort weiter — js/druckerfilter.js ist nach
   wie vor eine eigene Datei. Für die BAUFORM war sie nie ein Argument: Ein
   Filterkasten sieht nicht deshalb anders aus, weil er andere Fragen stellt.

   Was blieb, steht hier: die beiden Regler, die zu zweit unter einem Kopf
   stehen. */

/* Bauraum (Grundfläche/Höhe) und Baujahr (ab/bis) sind je ZWEI Regler unter
   EINEM Kriteriumskopf — zwei Köpfe für zwei Hälften derselben Frage wären
   eine Frage zu viel. Nebeneinander erst, wenn Platz ist; in der 420px-Spalte
   bliebe sonst je Regler eine Spur von 170px, und die Wertblase deckte den
   halben Weg ab. */
.dr-reglerpaar {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-m);
}
.dr-reglerpaarteil { min-width: 0; }
.dr-reglertitel {
  font-size: var(--label-small);
  color: var(--on-surface-variant);
  margin-bottom: var(--abstand-xxs);
}

/* ---------- Druckerdatenblatt (/drucker/<id>) ----------
   Nach den Bildvorlagen (05.09.2026). Alle Karten liegen auf
   --surface-container und tragen --karten-kante; die Kartentitel stehen in
   Schreibmaschinenschrift und Versalien, weil sie GLIEDERN und nichts
   benennen. Wer hier eine Flaeche aendert, misst die Texte darauf nach. */
.drucker-detail { max-width: 1120px; margin: 0 auto; }

/* ---- Kopf ---- */
.dd-kopf { margin-bottom: var(--abstand-2xl); }
/* Serif und mitwachsend wie jede andere Seitenueberschrift des Projekts. */
.dd-kopf h1 {
  font-family: var(--schrift-serif);
  font-size: clamp(28px, 4vw, 44px);
  font-weight: 600;
  line-height: var(--zeile-eng);
}
.dd-zusammenfassung {
  margin-top: var(--abstand-s);
  max-width: var(--lesebreite);
  font-size: var(--body-large);
  line-height: var(--zeile-text);
  color: var(--on-surface-variant);
}
.dd-wege {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--abstand-s); margin-top: var(--abstand-l);
}
/* Die Marktstand-Marke ist eine ANZEIGE, kein Bedienelement: kein Zeiger,
   kein Zustand, kein Fokus. Ihre Kante genuegt deshalb --outline-variant. */
.dd-marktstand {
  display: inline-flex; align-items: center;
  padding: var(--abstand-xs) var(--abstand-m);
  border: 1px solid var(--outline-variant);
  border-radius: var(--radius-pille);
  font-family: var(--schrift-mono);
  font-size: var(--label-medium);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--on-surface-variant);
}
.dd-marktstand--aktiv { color: var(--balken-gruen); border-color: var(--balken-gruen); }
.dd-marktstand--offen { font-style: italic; letter-spacing: .04em; }

/* ---- Ueberblick: Bildflaeche und Tabelle nebeneinander, Bandbreite darunter ---- */

/* Die Bandbreitenkarte ist die zweite Zeile dieses Abschnitts und laeuft ueber
   die volle Inhaltsbreite. Zwei Zeilen, nicht zwei Spalten: Zahl, Balken und
   Merkmalszeilen gehoeren nebeneinander, und dafuer ist in einer
   Sieben-Zwoelftel-Spalte kein Platz. */
.dd-ueberblick { display: grid; gap: var(--abstand-xl); }

/* Bildflaeche und Merkmalstabelle bleiben ein Paar. Die Flaeche traegt einen
   Platzhalter und einen Textverweis - allein in ihrer Zeile stuende rechts von
   ihr ueber die halbe Seitenbreite Leere. */
.dd-ueberblick-raster { display: grid; gap: var(--abstand-xl); }
@media (min-width: 900px) {
  .dd-ueberblick-raster {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    align-items: start;
  }
}
/* container-type, weil die Breite dieser Spalte NICHT an der Fensterbreite
   haengt: ab 900px ist sie 7 von 12 Rasterspalten, darunter die ganze Seite.
   Eine Medienabfrage traefe die Umbruchstelle der Tabelle darin nicht. */
.dd-ueberblick-spalte {
  display: grid; gap: var(--abstand-xl);
  container-type: inline-size;
}

/* KEIN fremdes Geraetebild: der Platzhalter im Seitenstil, darunter der
   Textverweis, den der Nutzer selbst oeffnet. Siehe js/seite-druckerdetail.js. */
.dd-bildflaeche {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--abstand-m);
  padding: var(--abstand-xl);
  min-height: 260px;
  background: var(--surface-container);
  border-radius: var(--radius-m);
  box-shadow: var(--karten-kante);
}
.dd-bildflaeche img { width: 100%; max-width: 220px; height: auto; opacity: .55; }
.dd-herstellerlink { font-size: var(--body-small); color: var(--on-surface-variant); }

/* ⛔ Ohne Zeichnung eine NEUTRALE Flaeche, keine falsche. Die vorhandene
   zeigt einen FDM-Drucker; auf der Seite eines Belichters waere sie eine
   Aussage ueber die Bauart, und zwar eine falsche.

   Dasselbe Muster wie die Fotoplaetze auf /wissen: Der gestrichelte Rahmen
   IST die Aussage dieser Flaeche und braucht deshalb die 3:1 eines grafischen
   Objekts - er steht auf --outline, nicht auf der Haarlinie
   --outline-variant. Flaeche und Kante der Karte entfallen dafuer: Zwei
   Rahmen um dasselbe Rechteck laesen sich als Fehler. */
.dd-bildflaeche--leer {
  border: 1px dashed var(--outline);
  background: none;
  box-shadow: none;
}
.dd-bild-leer {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--abstand-s);
  padding: var(--abstand-l);
  text-align: center;
}
.dd-bild-symbol { width: 28px; height: 28px; color: var(--on-surface-variant); }
.dd-bild-text {
  font-size: var(--body-small);
  color: var(--on-surface-variant);
  max-width: 24ch;
}

/* Der Kartentitel: Versalien in Schreibmaschinenschrift, Akzentfarbe. */
.dd-block-titel {
  font-family: var(--schrift-mono);
  font-size: var(--label-medium);
  font-weight: 500;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--primary);
  margin-bottom: var(--abstand-m);
}
.dd-karten-kopf {
  display: flex; align-items: center; gap: var(--abstand-xs);
  margin-bottom: var(--abstand-m);
}
.dd-karten-kopf .dd-block-titel { margin-bottom: 0; }

/* ---- Die Merkmalstabelle ---- */
.dd-tabelle {
  width: 100%; border-collapse: collapse;
  background: var(--surface-container);
  border-radius: var(--radius-m);
  box-shadow: var(--karten-kante);
  overflow: hidden;
  font-size: var(--body-medium);
}
.dd-tabelle thead th {
  font-family: var(--schrift-mono);
  font-size: var(--label-medium);
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--on-surface-variant);
}
.dd-tabelle th, .dd-tabelle td {
  padding: var(--abstand-m) var(--abstand-l);
  border-bottom: 1px solid var(--outline-variant);
  text-align: left;
  vertical-align: baseline;
}
.dd-tabelle tbody tr:last-child th, .dd-tabelle tbody tr:last-child td { border-bottom: 0; }
.dd-tabelle tbody th { font-weight: 400; color: var(--on-surface-variant); }
/* Der Wert steht in Schreibmaschinenschrift: Er ist ABGELESEN, nicht benannt. */
.dd-tabelle-wert { font-family: var(--schrift-mono); white-space: nowrap; }
.dd-tabelle-text { color: var(--on-surface-variant); }

/* ⛔ SCHMAL GIBT DIE TABELLE IHRE DRITTE SPALTE NACH UNTEN AB, statt
   waagerecht zu rollen. Gemessen brauchen die drei Spalten rund 441px; bei
   320px Fenster stehen 288px zur Verfuegung, und ohne diese Fassung lief die
   Tabelle um bis zu 120px ueber - verdeckt von overflow-x: clip am <html>,
   also lautlos.

   Dieselbe Entscheidung wie bei der Tabellenansicht der Liste (03.09.2026):
   Eine rollende Tabelle wird an einer Stelle abgeschnitten, die niemand
   waehlt. Der WERT bleibt dabei immer sichtbar - er ist die Auskunft, die
   Beschreibung ist der Zusatz. */
@container (max-width: 560px) {
  .dd-tabelle { display: block; }
  .dd-tabelle tbody { display: block; }
  /* Der Kopf bleibt im Baum: sichtbar traegt jede Zeile ihre Bezeichnung
     selbst, aber die Tabellensemantik soll nicht verlorengehen. */
  /* Wortgleich .sr-only aus css/basis.css - eine Klasse laesst sich an ein
     <thead> nur ueber das Markup haengen, und das Markup soll nicht wissen,
     wie breit die Flaeche ist. */
  .dd-tabelle thead {
    position: absolute; width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0;
    clip: rect(0 0 0 0); overflow: hidden; white-space: nowrap;
  }
  .dd-tabelle tbody tr {
    display: grid; grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--abstand-xs) var(--abstand-m);
    padding: var(--abstand-m) var(--abstand-l);
    border-bottom: 1px solid var(--outline-variant);
  }
  .dd-tabelle tbody tr:last-child { border-bottom: 0; }
  .dd-tabelle tbody th, .dd-tabelle tbody td { padding: 0; border-bottom: 0; }
  .dd-tabelle tbody th { grid-column: 1; }
  .dd-tabelle-wert { grid-column: 2; text-align: right; }
  .dd-tabelle-text { grid-column: 1 / -1; font-size: var(--body-small); }
  /* Wo nichts dazugehoert, bleibt die Zelle leer - dann auch ohne Luecke. */
  .dd-tabelle-text:empty { display: none; }
}

/* ---- Materialbandbreite ---- */
.dd-bandbreite {
  background: var(--surface-container);
  border-radius: var(--radius-m);
  padding: var(--abstand-l);
  box-shadow: var(--karten-kante);
}
.dd-bandbreite-zahl {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--abstand-s);
  margin-bottom: var(--abstand-m);
}
.dd-bandbreite-zahl strong {
  font-family: var(--schrift-serif);
  font-size: clamp(34px, 5vw, 52px);
  font-weight: 700;
  line-height: 1;
  color: var(--primary);
}
.dd-bandbreite-anzahl { font-size: var(--body-medium); color: var(--on-surface-variant); }
.dd-bandbreite .dr-balken { display: block; }

/* Innen zweispaltig: links Kicker, Zahl, Balken und Hinweissatz, rechts die
   Merkmalszeilen. align-items: start laesst BEIDE Spalten oben buendig
   abschliessen - ohne das streckte die kuerzere sich auf die Hoehe der
   anderen, und der Hinweissatz stuende in der Mitte seiner halben Karte.

   ⛔ Der Abstand kommt aus dem gap, nicht aus einem margin an den Kindern:
   Gestapelt ist es der Abstand zwischen den beiden Bloecken, nebeneinander
   die Lueckenbreite - ein margin-top an der Liste waere im einen Fall richtig
   und im anderen ein Versatz gegen die Zahl daneben. */
.dd-bandbreite-raster { display: grid; gap: var(--abstand-xl); }
@media (min-width: 900px) {
  .dd-bandbreite-raster {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--abstand-2xl);
    align-items: start;
  }
}
/* Gestapelt stehen Zahl und Balken ZUERST - sie sind die Aussage der Karte,
   die Merkmalszeilen sind ihre Begruendung. Das ist die Markupreihenfolge,
   also ist dafuer nichts zu tun; die Liste gibt nur ihren eigenen Abstand ab. */
.dd-bandbreite-raster .dd-punkte { margin-top: 0; }
/* Der Satz, der verhindert, dass der Balken als Kaufempfehlung gelesen wird.
   Er steht SICHTBAR unter der Zahl; die Herleitung der Gewichte liegt im
   Erklaerblatt am (i). Hinter einem Klick versteckt laese sich der Balken als
   Urteil ueber das Geraet - die Prusa MK4S staende mit 49 Prozent da wie eine
   schlechtere Maschine, obwohl ihr nur der geschlossene Bauraum fehlt. */
.dd-lesart {
  margin-top: var(--abstand-m);
  font-size: var(--body-medium);
  color: var(--on-surface-variant);
  max-width: var(--lesebreite);
}
/* Auf der Detailseite steht die Aufschluesselung offen da - kein Ueberfahren. */
.dd-punkte {
  list-style: none; padding-left: 0; margin-top: var(--abstand-m);
  display: flex; flex-direction: column; gap: var(--abstand-xs);
  font-size: var(--body-medium);
}

/* ---- Was dieses Geraet drucken kann ---- */
.dd-faehigkeit { margin-top: var(--abstand-3xl); }
.dd-abschnitt-kopf {
  display: flex; align-items: center; gap: var(--abstand-xs);
  margin-bottom: var(--abstand-l);
}
/* Neben einer Serifenueberschrift geht das 16px-Symbol des Filters unter -
   dort steht es neben 14px-Text. Die Trefferflaeche bleibt bei 26px. */
.dd-abschnitt-kopf .krit-info svg { width: 20px; height: 20px; }
.dd-faehigkeit h2, .dd-technisch h2 {
  font-family: var(--schrift-serif);
  font-size: var(--headline-small);
  font-weight: 600;
}
.dd-nicht-ermittelbar {
  display: flex; gap: var(--abstand-s); align-items: flex-start;
  padding: var(--abstand-l); background: var(--surface-container);
  border-radius: var(--radius-m); box-shadow: var(--karten-kante);
  color: var(--on-surface-variant); max-width: var(--lesebreite);
}
.dd-nicht-ermittelbar svg { width: 20px; height: 20px; flex-shrink: 0; }

/* DREI Gruppen, nicht zwei: 150 von 158 auswertbaren FDM-Geraeten haben
   mindestens ein eingeschraenktes Material.
   ⛔ SIE STEHEN UNTEREINANDER UEBER DIE VOLLE BREITE (17.09.2026), auf jeder
   Fensterbreite. Nebeneinander war die Moeglich-Karte auf rund die halbe
   Seitenbreite eingeschnuert, und ihre 20 bis 28 Materialpillen brachen in
   entsprechend viele Zeilen um. Ueber die volle Breite laufen sie in wenigen
   Zeilen, und die beiden kurzen Karten darunter kosten weniger Hoehe, als die
   Pillen vorher gebraucht haben.
   ⚠ Damit ist die frueher hier gemessene Begruendung zu `align-items: start`
   gegenstandslos: Es gibt keine zwei Spalten mehr, deren Hoehen auseinander-
   laufen koennten. */
.dd-koennen { display: grid; gap: var(--abstand-l); }
/* ⛔ `display: contents` und keine eigene Rasterzeile: Die beiden Karten
   sollen Kinder DESSELBEN Rasters sein wie die Moeglich-Karte, sonst traegt
   der Abstand zwischen ihnen zweimal (einmal aus jedem Raster) und die
   Gruppen stuenden ungleich weit auseinander. */
.dd-koennen-rechts { display: contents; }
.dd-gruppe {
  background: var(--surface-container);
  border-radius: var(--radius-m);
  border-top: 2px solid var(--outline-variant);
  padding: var(--abstand-l);
  box-shadow: var(--karten-kante);
}
.dd-gruppe-kopf {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--abstand-m); margin-bottom: var(--abstand-m);
}
.dd-gruppe h3 {
  display: flex; align-items: baseline; gap: var(--abstand-s);
  font-size: var(--title-medium); font-weight: 500;
}
.dd-gruppe-punkt {
  width: 8px; height: 8px; border-radius: 50%;
  background: currentColor; flex-shrink: 0; align-self: center;
}
.dd-zahl {
  font-family: var(--schrift-mono);
  font-size: var(--label-large);
  color: var(--on-surface-variant);
}
.dd-gruppe-zusatz { font-size: var(--body-small); color: var(--on-surface-variant); }
.dd-gruppe--ja { border-top-color: var(--balken-gruen); }
.dd-gruppe--ja h3 { color: var(--balken-gruen); }
.dd-gruppe--teils { border-top-color: var(--balken-gelb); }
.dd-gruppe--teils h3 { color: var(--balken-gelb); }
.dd-gruppe--nein { border-top-color: var(--error); }
.dd-gruppe--nein h3 { color: var(--error); }

/* Materialpillen: Schreibmaschinenschrift, weil ein Materialkuerzel ein
   abgelesener Wert ist und kein Satzteil. */
.dd-chips { display: flex; flex-wrap: wrap; gap: var(--abstand-xs); }
.dd-chip {
  display: inline-flex; align-items: center;
  padding: var(--abstand-xs) var(--abstand-m);
  border-radius: var(--radius-s);
  background: var(--surface-container-high);
  font-family: var(--schrift-mono);
  font-size: var(--label-medium);
  color: var(--on-surface);
  white-space: nowrap;
}
.dd-chip--teils { color: var(--warnung-erhoeht); }
.dd-chip--nein { color: var(--error); }
.dd-gruende { list-style: none; display: flex; flex-direction: column; gap: var(--abstand-m); }
.dd-gruende li { display: flex; gap: var(--abstand-m); align-items: flex-start; }
.dd-gruende .dd-chip { flex-shrink: 0; }
.dd-grund { font-size: var(--body-small); color: var(--on-surface-variant); }
.dd-leer { font-size: var(--body-medium); color: var(--on-surface-variant); font-style: italic; }

/* ---- Technische Daten ---- */
.dd-technisch { margin-top: var(--abstand-3xl); }
.dd-technisch h2 { margin-bottom: var(--abstand-l); }
.dd-blaetter { display: grid; gap: var(--abstand-xl); }
@media (min-width: 768px) {
  .dd-blaetter { grid-template-columns: 1fr 1fr; }
  /* Die Ausstattung steht ueber die volle Breite unter dem 2x2-Raster. */
  .dd-block--breit { grid-column: 1 / -1; }
}
/* ⛔ KEIN `align-self: start` (17.09.2026): Nebeneinanderstehende Karten
   werden auf die Hoehe der hoeheren gestreckt und schliessen unten buendig ab
   - Bauraum mit Temperaturen, Leistung & Geraeusch mit Mechanik. Der
   entstehende Leerraum in der kuerzeren Karte ist ausdruecklich hingenommen:
   Zwei Karten nebeneinander, die auf verschiedenen Hoehen enden, lesen sich
   als versehentlich verrutscht.
   ⚠ Die Zeilen bleiben dabei OBEN. Dafuer ist nichts zu tun, solange die
   Karte ein gewoehnlicher Block bleibt - wer sie zu einem Flex- oder
   Rasterkasten macht, verteilt die Zeilen ueber die gestreckte Hoehe und
   reisst genau die Zusage ein, ohne dass irgendwo etwas meldet.
   ⚠ Wo die Karten untereinander stehen (unter 768px), aendert das nichts:
   Eine Rasterzeile mit einem Kind ist so hoch wie dieses Kind. */
.dd-block {
  background: var(--surface-container); border-radius: var(--radius-m);
  padding: var(--abstand-l); box-shadow: var(--karten-kante);
}
.dd-liste { display: flex; flex-direction: column; }
.dd-zeile {
  display: flex; justify-content: space-between; gap: var(--abstand-m);
  padding: var(--abstand-s) 0; border-bottom: 1px solid var(--outline-variant);
  font-size: var(--body-medium);
}
.dd-zeile:last-child { border-bottom: 0; }
.dd-zeile dt { color: var(--on-surface-variant); }
/* Der Wert ist abgelesen, nicht benannt - dieselbe Regel wie in der Tabelle. */
.dd-zeile dd { text-align: right; font-family: var(--schrift-mono); }
/* Eine Luecke ist eine Information - sie steht sichtbar da, statt zu fehlen.
   Sie faellt aus der Schreibmaschinenschrift heraus, weil sie kein Wert ist. */
.dd-fehlt {
  color: var(--on-surface-variant); font-style: italic;
  font-family: var(--schrift);
}
.dd-fussnote {
  margin-top: var(--abstand-m); font-size: var(--body-small);
  color: var(--on-surface-variant); max-width: var(--lesebreite);
}

/* ---------- Gerätevergleich ----------
   Die Bezeichnungsspalte bleibt stehen, nur der Gerätebereich scrollt
   waagerecht — wie beim Filamentvergleich. Bei fünf Datenblättern
   nebeneinander verlöre man sonst beim Scrollen, welche Zeile man liest. */
.dv-rahmen { overflow-x: auto; margin-top: var(--abstand-xl); }
.dv-tabelle { border-collapse: collapse; min-width: 100%; }
.dv-tabelle th, .dv-tabelle td {
  padding: var(--abstand-s) var(--abstand-m);
  text-align: left;
  border-bottom: 1px solid var(--outline-variant);
  font-size: var(--body-medium);
  white-space: nowrap;
}
.dv-tabelle thead th { vertical-align: bottom; background: var(--surface); }
.dv-tabelle tbody th, .dv-ecke {
  position: sticky;
  left: 0;
  z-index: 2;
  background: var(--surface-container);
  font-weight: 500;
  color: var(--on-surface-variant);
  min-width: 200px;
}
.dv-tabelle thead .dv-ecke { z-index: 3; }
.dv-hinweis { display: block; font-size: var(--label-small); font-style: italic; opacity: .8; }
.dv-hersteller { display: block; font-size: var(--label-small); color: var(--on-surface-variant); }
.dv-modell { display: block; font-weight: 500; }
.dv-entfernen { border: 0; background: none; cursor: pointer; color: var(--on-surface-variant); padding: var(--abstand-xxs); }
.dv-entfernen svg { width: 16px; height: 16px; }
/* Nur wo es ein objektives Mehr gibt. Materialbandbreite, Gehäuse und
   Extruder bleiben unmarkiert — sonst würde der Balken zur Kaufempfehlung. */
.dv-best { font-weight: 700; color: var(--primary); }
.dv-einleitung { font-size: var(--body-large); color: var(--on-surface-variant); max-width: var(--lesebreite); }
.dv-leer { margin-top: var(--abstand-xl); padding: var(--abstand-2xl); text-align: center; background: var(--surface-container); border-radius: var(--radius-m); color: var(--on-surface-variant); }

/* ---------- Materialvergleich (/material-vergleich) ----------

   Die Tabelle teilt sich Rahmen, haftende Bezeichnungsspalte, Bestwert und
   Leerzustand mit dem Gerätevergleich — die `.dv-`-Klassen oben gelten also
   für BEIDE Vergleiche. Sie ein zweites Mal unter `mv-` zu führen hieße, zwei
   Tabellen zu pflegen, die gleich aussehen sollen. Hier steht nur, was der
   Materialvergleich zusätzlich braucht. */

/* Die Zellen tragen Balken und Sterne statt kurzer Zahlen und brauchen
   deshalb mehr Platz als die Gerätespalten. */
.mv-tabelle td { min-width: 170px; white-space: normal; }
.mv-tabelle thead th { min-width: 170px; white-space: normal; vertical-align: top; }

.mv-name { display: block; font-size: var(--title-medium); font-weight: 500; color: var(--primary); }
/* Der Einordnungssatz aus der Wissensbasis, klein unter dem Namen — er
   erklärt die Spalte, ohne eine eigene Vergleichszeile zu belegen. Vier
   Absätze nebeneinander wären keine Zeile, sondern vier Texte. */
.mv-kurz {
  display: block;
  margin-top: var(--abstand-xxs);
  max-width: 22ch;
  font-size: var(--label-small);
  font-weight: 400;
  line-height: var(--zeile-normal);
  color: var(--on-surface-variant);
}

/* Gruppentitel als eigene Zeile über die volle Breite. Sie gliedert 17 Zeilen
   in vier Blöcke; ohne sie stünde eine lange Liste ohne Zäsur da. */
.mv-gruppe th {
  position: static;
  min-width: 0;
  padding-top: var(--abstand-xl);
  font-size: var(--label-medium);
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--primary);
  background: var(--surface);
}

/* Bestwert im Klartext. Er steht ZUSÄTZLICH zur Einfärbung durch .dv-best,
   nicht statt ihrer: Der Zahlenwert im Balken ist ohnehin fett, von der
   Hervorhebung bliebe sonst allein der Farbton — und Farbe ist hier nie die
   einzige Information. Wortlaut bewusst „am geringsten" und nicht „am
   besten": Weniger Aufwand ist nicht dasselbe wie das bessere Material. */
.mv-bestmarke {
  display: inline-block;
  margin-top: var(--abstand-xxs);
  padding: 0 var(--abstand-xs);
  border-radius: var(--radius-s);
  background: var(--primary-container);
  color: var(--on-primary-container);
  font-size: var(--label-small);
  font-weight: 500;
  white-space: nowrap;
}

/* Kein Sieger, sondern der Weg zum Werkzeug, das die Frage beantworten kann. */
.mv-schluss {
  margin-top: var(--abstand-2xl);
  padding: var(--abstand-xl);
  background: var(--surface-container);
  border-radius: var(--radius-m);
  max-width: var(--lesebreite);
}
.mv-schluss h2 { font-size: var(--title-large); font-weight: 500; }
.mv-schluss p {
  margin: var(--abstand-s) 0 var(--abstand-l);
  color: var(--on-surface-variant);
  line-height: var(--zeile-text);
}

/* Gemerkte Geräte — eigener Abschnitt, nicht zwischen den Produkten. */
.mz-drucker { margin-top: var(--abstand-3xl); }
.mz-drucker h2 { display: flex; align-items: baseline; gap: var(--abstand-s); font-size: var(--headline-small); font-weight: 400; }
.mz-drucker-zahl { font-size: var(--label-medium); color: var(--on-surface-variant); }
.mz-drucker-liste { display: flex; flex-direction: column; gap: var(--abstand-s); margin-top: var(--abstand-l); }
.mz-drucker-zeile { display: flex; align-items: center; justify-content: space-between; gap: var(--abstand-m); background: var(--surface-container); border-radius: var(--radius-m); padding: var(--abstand-m) var(--abstand-l); }
.mz-drucker-zeile a { text-decoration: none; color: inherit; flex: 1; min-width: 0; }
.mz-drucker-name { display: block; font-weight: 500; }
.mz-drucker-daten { display: block; font-size: var(--body-small); color: var(--on-surface-variant); }

/* Abschnitt ohne Bild: volle Breite statt leerer Rasterspalte. Ein sichtbarer
   Platzhalter neben vier echten Fotos sähe nach unfertig aus, eine bewusst
   textlastige Sektion nicht. */
.start-thema--ohne-bild .thema-raster { display: block; }
.start-thema--ohne-bild .thema-text-spalte { max-width: var(--lesebreite); }
/* Fertige Grafik statt Foto: mittig, mit Luft, und gedämpft — sie ist ein
   Symbol, kein Bild des Geräts.

   ⚠️ Diese Bauart kommt im heutigen Markup NICHT vor — kein Thema in
   js/seite-start.js trägt `bild.grafik`. Die abgesetzte Fläche ist trotzdem
   gefallen (14.08.2026): Sie war die letzte Stelle, an der eine Bildfläche
   der Startseite noch einen eigenen Grund und damit eine sichtbare Kante
   bekommen hätte. Bliebe sie stehen, brächte der erste Abschnitt, der diese
   Bauart wiederbelebt, die Kante ohne Zutun zurück. Eine Maske bekommt sie
   nicht: Die Grafik ist ein freigestelltes Symbol auf durchsichtigem Grund,
   sie hat gar keine Kante zum Ausblenden. */
.bildflaeche--grafik {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1 / 1;
}
.bildflaeche--grafik img { width: 55%; height: auto; opacity: .5; }

/* ---------- Detailseite: Preisherleitung, Druckprofil, Angebotstabelle,
              Vertrauensblock (02.09.2026, Bildvorlagen 3 und 4) ---------- */

/* Die Preisrechnung als Fußzeile am Ende der Seite (12.09.2026).

   ⚠️ Bis dahin war das eine eigene Karte unmittelbar unter dem Kopf, mit
   vier großen Feldern und dem Ergebnis in Serif und Akzentfarbe. Den Platz
   hat der Preisverlauf bekommen; die Angabe selbst ist vollständig erhalten,
   nur klein und zuletzt.

   ⛔ SIE IST KEINE KARTE. Kein eigener Grund, keine Kante, kein Polster —
   sonst wäre sie wieder ein Abschnitt. Maße und Farbe sind die der übrigen
   Fußhinweise der Seite (.at-fussnote, .harz-fussnote). */
.detail-preisfuss {
  margin: 0;
  font-size: var(--body-small);
  color: var(--on-surface-variant);
  max-width: var(--lesebreite);
}
/* Die Rechnung selbst in Schreibmaschinenschrift: Sie besteht aus abgelesenen
   Werten, der Satz dahinter benennt. Dieselbe Trennung wie überall. */
.pf-rechnung {
  font-family: var(--schrift-mono);
  font-variant-numeric: tabular-nums;
  color: var(--on-surface);
}

/* Das Druckprofil: vier Werte als Raster, darunter die Balkenskala. */
.dp-felder {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--abstand-l);
  margin: 0 0 var(--abstand-xl);
}
.dp-feld { display: flex; flex-direction: column; gap: var(--abstand-xxs); min-width: 0; }
.dp-bezeichnung { font-size: var(--body-medium); color: var(--on-surface-variant); }
.dp-wert {
  margin: 0;
  font-family: var(--schrift-mono);
  font-size: var(--title-medium);
  font-variant-numeric: tabular-nums;
}
/* „Richtwert" — der Wert stammt aus der Wissensbasis des Materials, nicht vom
   Hersteller. Diese Kennzeichnung hing bis zum 02.09.2026 am Druckerschema
   und darf mit ihm nicht verlorengehen: Ohne sie liest sich ein abgeleiteter
   Wert wie eine Herstellerangabe. */
.dp-zusatz {
  display: block;
  font-family: var(--schrift);
  font-size: var(--body-small);
  font-style: italic;
  color: var(--on-surface-variant);
}

/* ---------- Angebote im Vergleich (24.09.2026, Bildvorlage) ----------

   Eine Karte je Angebot, nebeneinander, in der Reihenfolge der einen
   Rangfolge (PD.berechnung.sortierwert). Bis zum 24.09.2026 stand hier eine
   Tabelle (.at-*), die Karten tragen dieselben Zahlen je Angebot.

   ⛔ DER ABSCHNITT IST KEINE KARTE, die Angebote sind es: Überschrift und
   Karten stehen auf der Seitenfläche wie in der Vorlage. */
.dw-kopf {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--abstand-xs) var(--abstand-l);
  margin-bottom: var(--abstand-l);
}
/* Nach `.detail-weitere h2` (gleiches Gewicht): Den Abstand trägt die Zeile. */
.dw-kopf h2 { margin-bottom: 0; }
.dw-ordnung { font-size: var(--body-small); color: var(--on-surface-variant); }

/* ⛔ auto-fill, NICHT auto-fit: Bei zwei Angeboten blieben mit auto-fit zwei
   Karten über die ganze Seitenbreite gezogen stehen. `min(220px, 100%)`
   statt blankem 220px — eine Mindestbreite ist sonst auch dann eine
   Untergrenze, wenn weniger Platz da ist (CLAUDE.md, „Mindestbreiten"). */
.detail-angebote {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr));
  gap: var(--abstand-l);
}
.angebot-karte {
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: var(--abstand-l);
  border-radius: var(--radius-m);
  background-color: var(--surface-container);
  container-type: inline-size;
}
/* Der Bestpreis trägt eine KANTE in --primary (Grafik, 3:1 gegen die Karte
   weit übertroffen) — dazu die Marke „Bestpreis" als WORT und den gefüllten
   Knopf. Die Fläche bleibt dieselbe, damit kein Text auf eine neue Fläche
   kommt, die niemand nachgemessen hat. */
.angebot-karte--bestes { box-shadow: inset 0 0 0 2px var(--primary); }

/* Alles über der Trennlinie. Es wächst und schiebt Konditionen und Knopf an
   den Fuß — so stehen Zeilen und Knöpfe einer Reihe auf gleicher Höhe, auch
   wenn eine Karte eine Marke oder eine Näherungszeile mehr trägt. */
.ak-kopf { flex: 1 0 auto; padding-bottom: var(--abstand-m); }
.ak-marken {
  display: flex;
  flex-wrap: wrap;
  gap: var(--abstand-xs);
  min-height: 24px;
  margin-bottom: var(--abstand-m);
}
.ak-marke--bestpreis { background: var(--primary-container); color: var(--on-primary-container); }
/* Dieselbe Rolle wie die Versandmarke der Kachel: dieselbe Aussage. */
.ak-marke--versandfrei { background: var(--versand-frei-flaeche); color: var(--versand-frei-tinte); }
/* Wie .badge--eingeschraenkt: Warnfarbe als Text UND Kante, das Wort trägt. */
.ak-marke--aus {
  background: var(--surface-container-high);
  color: var(--warnung-erhoeht);
  box-shadow: inset 0 0 0 1px var(--warnung-erhoeht);
}
.ak-shop {
  font-size: var(--title-medium);
  font-weight: 600;
  overflow-wrap: anywhere;
}
/* Der Vergleichspreis des Angebots: Schrift, Größe und Farbe wie der Preis
   der Kachel (Serif, 28px, --primary). */
.ak-preis {
  margin-top: var(--abstand-xs);
  font-family: var(--schrift-serif);
  font-size: var(--headline-medium);
  font-weight: 700;
  line-height: var(--zeile-eng);
  color: var(--primary);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
/* Eine Fremdwährung („24,90 CHF/kg") liefe auf der schmalsten Karte über —
   eine CONTAINERabfrage, weil die Kartenbreite an der Spaltenzahl hängt. */
@container (max-width: 230px) {
  .ak-preis { font-size: var(--title-large); }
}
.ak-abstand {
  min-height: 1.5em;
  margin-top: var(--abstand-xxs);
  font-family: var(--schrift-mono);
  font-size: var(--label-medium);
  color: var(--on-surface-variant);
}

.ak-liste {
  margin: 0;
  padding-top: var(--abstand-m);
  border-top: 1px solid var(--outline-variant);
}
.ak-zeile {
  display: flex;
  justify-content: space-between;
  gap: var(--abstand-s);
  padding: var(--abstand-xxs) 0;
  font-size: var(--body-medium);
}
.ak-zeile dt { color: var(--on-surface-variant); }
.ak-zeile dd {
  margin: 0;
  text-align: right;
  font-family: var(--schrift-mono);
  font-variant-numeric: tabular-nums;
}
/* 44px wie „Zum Shop" auf der Kachel (WCAG 2.5.5). */
.ak-knopf { width: 100%; min-height: 44px; margin-top: var(--abstand-m); }
.ak-pfeil { margin-left: var(--abstand-xxs); }
.ak-kein-link {
  margin-top: var(--abstand-xs);
  font-size: var(--body-small);
  color: var(--on-surface-variant);
  text-align: center;
}

/* Nicht lieferbar: Shopname und Preis verlieren die Auszeichnung, die Marke
   „Nicht lieferbar" trägt die Aussage. Bewusst KEIN opacity — das drückte
   Preis und Marke gleichermaßen unter AA, ohne mehr zu sagen. */
.angebot-karte--aus .ak-shop,
.angebot-karte--aus .ak-preis { color: var(--on-surface-variant); }

.at-fussnote {
  margin-top: var(--abstand-m);
  font-size: var(--body-small);
  color: var(--on-surface-variant);
}

/* Der Vertrauensblock — seit dem 24.09.2026 über die volle Breite vor der
   Preisfußzeile. */
.detail-vertrauen .vertrauen-liste {
  margin: var(--abstand-m) 0 0;
  padding-left: var(--abstand-l);
  font-size: var(--body-medium);
  line-height: var(--zeile-text);
  color: var(--on-surface-variant);
}
.detail-vertrauen .vertrauen-liste li + li { margin-top: var(--abstand-xs); }
.vertrauen-link {
  display: inline-block;
  margin-top: var(--abstand-m);
  font-size: var(--body-medium);
  font-weight: 500;
}

/* ==================================================================
   Wissensseite „So arbeitet ein 3D-Drucker" (/wissen, 03.09.2026)
   ==================================================================

   Visuell führend, textlich knapp: Zeichnung, Balkenstapel und
   Schichtschnitt tragen die Aussage, der Text ordnet sie ein. Alle Farben
   kommen aus css/theme.css; die Seite legt kein einziges Literal an.

   ⛔ JEDER DIAGRAMMBALKEN TRÄGT SEINEN TEXT. Der Schichtschnitt besteht nicht
   aus reinen Farbflächen — die Farbe ordnet ein, die Beschriftung sagt, was
   gemeint ist. Wer eine Beschriftung entfernt, macht aus einer Auskunft eine
   Dekoration. ⚠️ Bis zum 06.09.2026 galt derselbe Satz auch für den
   Hotend-Stapel; der ist entfallen, weil die Explosionszeichnung dieselben
   Bauteile einzeln führt. */

.wissen-seite {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-4xl);
}

.wissen-kopf { display: flex; flex-direction: column; gap: var(--abstand-s); }

.wissen-titel {
  margin: 0;
  font-family: var(--schrift-serif);
  /* Wächst mit dem Fenster wie jeder Seitentitel des Projekts. */
  font-size: clamp(28px, 4vw, 44px);
  font-weight: 600;
  line-height: var(--zeile-eng);
  color: var(--on-surface);
}

.wissen-einleitung {
  margin: 0;
  max-width: var(--lesebreite);
  font-size: var(--body-large);
  color: var(--on-surface-variant);
}

/* Alle Abschnitte sind Sprungziele — auch die, auf die heute nichts zeigt:
   Eine geteilte Adresse /wissen#begriffe soll dort ankommen und nicht unter
   der klebenden Kopfleiste. */
.wissen-abschnitt {
  scroll-margin-top: calc(var(--kopf-hoehe) + var(--abstand-l));
  display: flex;
  flex-direction: column;
  gap: var(--abstand-xl);
}

/* Überschrift und gedämpfter Zusatz stehen nebeneinander und brechen
   zusammen um; auf schmalen Fenstern rutscht der Zusatz unter die
   Überschrift, statt sie zu quetschen. */
.wissen-kopfzeile {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--abstand-xs) var(--abstand-m);
}

.wissen-h2 {
  margin: 0;
  font-family: var(--schrift-serif);
  font-size: var(--headline-medium);
  font-weight: 600;
  line-height: var(--zeile-eng);
  color: var(--on-surface);
}

.wissen-zusatz {
  margin: 0;
  font-size: var(--body-large);
  color: var(--on-surface-variant);
}

.wissen-fussnote {
  margin: 0;
  max-width: var(--lesebreite);
  font-size: var(--body-small);
  color: var(--on-surface-variant);
}

/* ---------- Bildflächen ----------

   Vier Stück: FDM-Drucker, Resin-Drucker, Hotend, Harzwanne. Solange kein
   Foto hinterlegt ist, steht eine leere Fläche mit gestricheltem Rahmen da.

   ⛔ SIE KÜNDIGT KEINE BEDIENUNG AN. Kein „hier ablegen", kein „browse
   files", kein cursor: pointer — es gibt keinen Upload, und was aussieht wie
   ein Bedienelement, muss etwas tun. Der gestrichelte Rahmen sagt „hier fehlt
   noch etwas", und das ist die ganze Aussage. */
.wb-flaeche {
  /* position: relative ist der Bezugsrahmen des Videos, das absolut darin
     liegt. Der Platzhalter bleibt dadurch ein Flex-Kind und zentriert sich
     weiter selbst — auch dann noch, wenn das Video im Fehlerfall entfernt
     wurde. */
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border-radius: var(--radius-m);
  background: var(--surface-container);
  box-shadow: var(--karten-kante);
  aspect-ratio: 3 / 2;
}
.wb-flaeche[data-platzhalter="ja"] {
  /* Der gestrichelte Rahmen IST die Aussage dieser Fläche; er braucht deshalb
     die 3:1 eines grafischen Objekts und steht auf --outline, nicht auf der
     Haarlinie --outline-variant. Fläche und Kante der Karte entfallen dafür:
     Zwei Rahmen um dasselbe Rechteck läsen sich als Fehler. */
  border: 1px dashed var(--outline);
  background: none;
  box-shadow: none;
}
.wb-foto { width: 100%; height: 100%; object-fit: cover; }

/* Das Video der beiden Verfahrenskarten.

   ⛔ pointer-events: none IST PFLICHT. Die ganze Karte ist ein <a> auf ihren
   Abschnitt (#fdm / #resin); ein Video, das Zeiger annimmt, fienge genau die
   Klicks ab, die in der Mitte der Karte landen — also die meisten. Ohne
   `controls` gibt es daran ohnehin nichts zu bedienen.

   ⚠️ object-fit: cover, wie beim Foto daneben. Die Dateien sind quadratisch
   (1920 × 1920), die Fläche ist 3:2 — es fällt oben und unten je ein Sechstel
   weg. Gemessen liegt bei beiden Videos das Motiv im mittleren Band, und
   abgeschnitten wird überwiegend leerer Rasterhintergrund. `contain` ließe
   statt dessen links und rechts je 16,7 % der Fläche frei. */
.wb-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
}
.wb-platzhalter {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--abstand-s);
  padding: var(--abstand-l);
  text-align: center;
}
/* ⛔ Diese Zeile ist Pflicht, sobald der Platzhalter `hidden` tragen kann.
   `display: flex` einer Klasse schlägt das `display: none` des Browsers für
   [hidden] — ohne die Rücknahme läge der verborgene Platzhalter dauerhaft
   sichtbar über dem Video, und zwar ohne dass irgendwo etwas meldete. Dieselbe
   Falle hat das Projekt schon bei .such-treffer, .zaehlmarke, .feld-rest und
   .farbvorschlaege genauso gelöst. */
.wb-platzhalter[hidden] { display: none; }
.wb-symbol { width: 28px; height: 28px; color: var(--on-surface-variant); }
.wb-text {
  font-family: var(--schrift-mono);
  font-size: var(--label-large);
  color: var(--on-surface-variant);
}

/* ---------- Die beiden Verfahrenskarten ---------- */

.wv-raster {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(360px, 100%), 1fr));
  gap: var(--abstand-xl);
}

/* Die ganze Karte ist der Sprung — nicht nur der Titel. Sie trägt deshalb den
   Fokusring, und ihr Inhalt bleibt in der Textfarbe der Seite: Ein blau
   gefärbter Kartentext läse sich als Linkliste. */
.wv-karte {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-l);
  padding: var(--abstand-l);
  border-radius: var(--radius-l);
  background-color: var(--surface-container);
  box-shadow: var(--karten-kante);
  color: inherit;
  text-decoration: none;
  /* Zustandsebene auf einer EIGENEN Fläche: als Verlauf über der Füllung,
     nicht als background — sonst schiene die Seite durch die Karte. Der
     durchsichtige Verlauf im Ruhezustand ist die Bedingung für den weichen
     Übergang. */
  background-image: linear-gradient(transparent, transparent);
  transition: background-image var(--dauer-kurz) ease;
}
.wv-text { display: flex; flex-direction: column; gap: var(--abstand-s); }
.wv-kopf {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--abstand-s);
}
/* Verfahrenskennzeichen — dieselbe Bauart wie das Materialkennzeichen der
   Produktkachel: Mono, klein, auf der Containerfläche der Markenrolle. */
.wv-tag {
  font-family: var(--schrift-mono);
  font-size: var(--label-medium);
  font-weight: 500;
  padding: 2px var(--abstand-s);
  border-radius: var(--radius-s);
  background: var(--primary-container);
  color: var(--on-primary-container);
}
.wv-titel {
  margin: 0;
  font-size: var(--title-large);
  font-weight: 500;
  color: var(--on-surface);
}
.wv-satz { margin: 0; font-size: var(--body-large); color: var(--on-surface-variant); }
.wv-fakten { display: flex; flex-wrap: wrap; gap: var(--abstand-s); }
/* Abgelesene Werte, also Schreibmaschinenschrift — dieselbe Regel wie überall
   im Projekt. */
.wv-fakt {
  font-family: var(--schrift-mono);
  font-size: var(--label-medium);
  padding: var(--abstand-xxs) var(--abstand-s);
  border-radius: var(--radius-s);
  background: var(--surface-container-highest);
  color: var(--on-surface-variant);
}

/* ---------- Die Verfahrenskarte ist der Schalter ---------- */

/* ⚠️ Der Schalter ist ein UMRANDETER Knopf und kein blankes Wort: Er steht
   mitten im Fließtext der Karte, und dort liest sich ein gefärbtes Wort als
   Link. Die Kontur trägt die Grenze des Bedienelements (WCAG 1.4.11, 3:1)
   und kommt aus `currentColor` — damit folgt sie der Schriftfarbe der Karte
   und stimmt in der geöffneten Lage (Markenfläche) von selbst mit, ohne eine
   zweite Farbregel.
   ⚠️ NICHT die Klasse `.knopf` nehmen: Deren `::after` ist die
   Zustandsebene, und hier trägt `::after` bereits die Trefferfläche. */

/* ⛔ DER KNOPF DECKT DIE GANZE KARTE AB, die Karte selbst ist keiner. Sie
   trägt eine Bildfläche aus <div> und <video>, und ein <button> nimmt nur
   Phrasing Content — beim Bauen genau so zugeschnappt: Die Karte sah richtig
   aus und war ungültiges Markup. Dieselbe Technik wie `.ok-hauptlink` der
   Merkzettel-Ordner und `.dr-hauptbereich` der Gerätekachel. */
.wv-karte { position: relative; }
.wv-schalter {
  position: relative;
  z-index: 1;
  align-self: flex-start;
  display: flex;
  align-items: center;
  gap: var(--abstand-xs);
  margin-top: var(--abstand-xxs);
  min-height: 40px;
  padding: 0 var(--abstand-l);
  border: 1px solid currentColor;
  border-radius: var(--radius-pille);
  background: none;
  font: inherit;
  font-size: var(--label-large);
  font-weight: 500;
  color: var(--primary);
  cursor: pointer;
  text-align: left;
}
.wv-schalter::after {
  content: "";
  position: absolute;
  inset: 0;
}
/* ⛔ Der Fokusring gehört um die GANZE Karte, nicht um das Wort: Die
   Trefferfläche ist die Karte, und ein Ring um zwei Wörter mitten darin
   sagte nicht, was der Tabstopp gerade meint. */
.wv-schalter:focus-visible { outline: none; }
.wv-karte:has(.wv-schalter:focus-visible) {
  outline: 3px solid var(--primary);
  outline-offset: 3px;
}
/* ⛔ BEIDE WÖRTER STEHEN IM MARKUP, sichtbar ist immer genau eines. Der Text
   wird damit nicht in JavaScript zusammengesetzt — er steht im Stylesheet
   nebeneinander und lässt sich als Paar lesen. */
.wv-schalter[aria-expanded="false"] .wv-schalter-wort--zu,
.wv-schalter[aria-expanded="true"] .wv-schalter-wort--auf { display: none; }
.wv-chevron {
  width: 20px;
  height: 20px;
  fill: currentColor;
  transition: transform var(--dauer-mittel) ease;
}
.wv-schalter[aria-expanded="true"] .wv-chevron { transform: rotate(180deg); }

/* Die geöffnete Karte trägt die Markenfläche — sie ist der Zustand, den man
   auf einen Blick sehen soll, und Farbe allein trägt ihn nicht: Der
   Schalterhinweis wechselt zugleich sein Wort und sein Chevron.
   ⚠️ `:has()` hält den Zustand an EINER Stelle, nämlich am Knopf. Ihn
   zusätzlich an die Karte zu schreiben hieße, denselben Zustand zweimal zu
   führen. Chrome 105 / Safari 15.4 / Firefox 121 — dieselbe Generation wie
   die Containerabfragen, die das Projekt ohnehin voraussetzt. */
.wv-karte:has(.wv-schalter[aria-expanded="true"]) {
  background-color: var(--secondary-container);
  color: var(--on-secondary-container);
}
.wv-karte:has(.wv-schalter[aria-expanded="true"]) .wv-satz,
.wv-karte:has(.wv-schalter[aria-expanded="true"]) .wv-schalter { color: inherit; }
/* Die Zustandsebene folgt dem Zeiger über der ganzen Karte, nicht nur über
   dem Wort — die Karte IST die Trefferfläche. */
.wv-karte:has(.wv-schalter:hover) {
  background-image: linear-gradient(var(--zustand-hover), var(--zustand-hover));
}
.wv-karte:has(.wv-schalter:active) {
  background-image: linear-gradient(var(--zustand-aktiv), var(--zustand-aktiv));
}

/* ---------- Auf- und zuklappbarer Fachbereich ---------- */

/* ⛔ DIE HÖHE FÄHRT ÜBER DIE RASTERZEILE (0fr → 1fr), nicht über max-height
   und nicht über eine in JavaScript gemessene Pixelhöhe. Dieselbe Bauart wie
   die Klappabschnitte der Detailseite: Die Zeile misst sich in jeder Lage
   selbst, also stimmt sie bei jedem Umbruch und auf jeder Fensterbreite —
   ohne eine einzige Zahl.
   ⚠️ Der Preis ist Chrome 117 / Safari 17.2 / Firefox 120. Wo die Animation
   der Rasterzeile fehlt, klappt der Bereich trotzdem auf und zu, nur ohne
   Gleitweg: `0fr` ist eine gewöhnliche Rasterangabe. Der Rückfall ist „kein
   Übergang", nie „geht nicht". */
/* Der Klappbereich trägt genau ein Kind, die Hülle — er braucht das
   Spalten-Flexlayout von .wissen-abschnitt also nicht und stellt es ab. */
.wissen-klapp { display: block; }

/* ⛔ DER OFFENE ZUSTAND IST DIE GRUNDREGEL, der zugeklappte die Ausnahme —
   dieselbe Richtung wie bei den Klappabschnitten der Detailseite. Beide
   Bereiche lesen dieselbe Bauart; sie umgekehrt zu schreiben hieße, zwei
   Fassungen derselben Mechanik zu führen.

   ⚠️ MESSFALLE, BEIM BAUEN ZUGESCHNAPPT: In headless Chrome laufen
   Übergänge nicht ab, und `getComputedStyle` liefert deshalb den Wert bei
   t = 0 — hier also dauerhaft `0px`, egal wie lange man wartet. Der Bereich
   sah eine Stunde lang aus wie kaputt und war es nie; erst mit
   stillgelegten Übergängen misst man 1445 px. Es ist dieselbe Falle, die
   für Farben schon dokumentiert ist: Wer Layout misst, das an einer
   Transition hängt, legt sie vorher still. */
.wk-huelle {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows var(--dauer-mittel) ease;
}
.wissen-klapp:not([data-wk-offen]) .wk-huelle { grid-template-rows: 0fr; }
/* ⛔ `min-height: 0` ist Pflicht: Eine Rasterzeile wird von sich aus nie
   kleiner als ihr Inhalt, und ohne die Rücknahme bliebe der Bereich offen. */
.wk-fenster {
  overflow: hidden;
  min-height: 0;
  /* Zugeklappt ist der Inhalt auch für Tastatur und Vorlesehilfe weg. Eine
     Zeile der Höhe 0 mit Zuschnitt ist nur OPTISCH weg: Der Text bliebe im
     Baum, die Tabulatortaste liefe hindurch, und eine Vorlesehilfe läse einen
     Bereich vor, den der Besucher ausdrücklich zugeklappt hat.
     ⚠️ Die Verzögerung gehört zum SCHLIESSEN und nur dorthin: Beim Zuklappen
     bleibt der Inhalt sichtbar, bis die Zeile unten ist; beim Aufklappen ist
     er sofort da. */
  visibility: visible;
  transition: visibility 0s;
}
.wissen-klapp:not([data-wk-offen]) .wk-fenster {
  visibility: hidden;
  transition: visibility 0s linear var(--dauer-mittel);
}
/* ⛔ DER INNENABSTAND GEHÖRT HIERHER, NICHT AN DAS ZUGESCHNITTENE FENSTER.
   Bei `box-sizing: border-box` kann eine Höhe von 0 den Innenabstand nicht
   auffressen — er ist die Untergrenze des Kastens. Am falschen Element
   bliebe unter jeder zugeklappten Karte ein Streifen Leere stehen, den
   niemand erklären kann. Dieselbe Falle wie auf der Detailseite. */
.wk-inhalt {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-3xl);
  padding-top: var(--abstand-xl);
}

/* ---------- Das interaktive Modell des FDM-Druckers ---------- */

.wm-modell {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-l);
}
.wm-kopfzeile {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--abstand-m);
}
.wm-kopftext { display: flex; flex-direction: column; gap: var(--abstand-xxs); }
.wm-titel {
  margin: 0;
  font-family: var(--schrift-serif);
  font-size: var(--headline-small);
  font-weight: 600;
  color: var(--on-surface);
}
.wm-satz {
  margin: 0;
  max-width: var(--lesebreite);
  font-size: var(--body-medium);
  color: var(--on-surface-variant);
}
/* ⛔ `.wm-zurueck[hidden]` BRAUCHT SEINE EIGENE REGEL. Das Projekt führt
   keine globale [hidden]-Regel, und das `display: flex` der Klasse `.knopf`
   schlägt die Vorgabe des Browsers — ohne diese Zeile stünde der Weg zurück
   dauerhaft da, auch in der Gesamtansicht, wo es nichts zurückzugehen gibt.
   Gemessen: `hidden=true`, `display: flex`, sichtbar. Dieselbe Falle wie bei
   .wb-platzhalter, .wx-detail, .such-treffer, .zaehlmarke und .feld-rest. */
.wm-zurueck { flex: none; }
.wm-zurueck[hidden] { display: none; }

/* ---------- Arena: Zeichnung und Stückliste nebeneinander ----------

   ⛔ DIE TOKENS STEHEN HIER UND NICHT MEHR AN DER BÜHNE (07.09.2026). Sie
   werden seither von zwei Seiten gelesen — die Bühne rechnet ihre Größe
   daraus, die Arena ihre Breitengrenze und ihre Fahrt. Zweimal deklariert
   wären es zwei Zahlen für dieselbe Sache, und die erste Änderung ließe sie
   auseinanderlaufen.

   ⛔ DIE ARENA IST IMMER ZWEISPALTIG, ES WIRD NIE UMGESCHALTET. Im
   Gesamtbild steht die Bildspalte über `translateX` in der Mitte der Arena
   und die Listenspalte unsichtbar darunter; beim Öffnen läuft sie auf 0 und
   die Liste blendet auf. Eine Umschaltung zwischen ein- und zweispaltig wäre
   eine Layoutänderung und damit genau der Sprung, den die Bewegung vermeiden
   soll — animierbar ist sie ohnehin nicht.

   ⛔ DIE ZEICHNUNG ÄNDERT DABEI IHRE GRÖSSE NICHT, sie wandert nur. Das ist
   der Grund für die Breitengrenze eine Zeile weiter unten und zugleich der
   Grund für die Schwelle von 1280px: Eine halbe Spalte trägt die Bühne in
   voller Größe erst, wenn sie mindestens `--wm-hoehe * 3/4` breit ist.
   Darunter stünde die GESAMTANSICHT kleiner da als heute — und die ist der
   Einstieg in alles Weitere. Dort geht die Liste deshalb nach unten. */
.wm-arena {
  /* ⛔ EINE KURVE UND EINE DAUER FÜR BEIDE BLÄTTER. Nur dann sind Zoom und
     Gegenzoom kongruent; zwei verschiedene Kurven machten aus der Fahrt eine
     Kreuzblende, und der Bezug zwischen Ausschnitt und Detail ginge genau in
     der Mitte der Bewegung verloren.
     ⚠️ Die Fahrt der Bildspalte und das Aufblenden der Liste nehmen dieselbe
     — sonst wären es zwei Bewegungen nebeneinander statt einer. */
  --wm-kurve: cubic-bezier(0.42, 0, 0.18, 1);

  /* ⛔ ZWEI PHASEN NACHEINANDER, NICHT MEHR ZUGLEICH (07.09.2026, nachm.).
     Beim Öffnen zoomt die Zeichnung ZUERST an ihrer Stelle in den Ausschnitt
     — sie bleibt dabei mittig stehen —, und erst danach fährt sie nach links,
     während die Stückliste aufblendet. Beim Schließen läuft es rückwärts:
     erst zurück in die Mitte, dann herauszoomen.

     ⛔ DIE VERZÖGERUNG GEHÖRT ZUM ZIELZUSTAND, und das ist der ganze Kniff
     der Richtungsabhängigkeit: Beim Wechsel gilt die Transition des
     Zustands, in den gewechselt wird. Der Zoom trägt seinen Verzug deshalb
     in der Regel für den GESCHLOSSENEN Zustand (er wirkt beim Schließen) und
     0 in der für den offenen; die Fahrt genau umgekehrt. Wer einen Verzug in
     die falsche Regel schreibt, dreht die Reihenfolge einer Richtung um —
     und die andere sieht weiter richtig aus.

       Öffnen    Zoom    0 …………… 460
                 Fahrt          380 …… 660
       Schließen Fahrt   0 …… 280
                 Zoom          200 …………… 660

     ⚠️ DIE ÜBERLAPPUNG IST DER GRUND, WARUM ES NICHT ANEINANDERSTÖSST: Die
     zweite Phase fährt an, während die erste noch ausläuft (beide Enden sind
     durch --wm-kurve weich). Sie steht als EINE Zahl da, und beide
     Verzögerungen rechnen daraus — wer sie ändert, ändert beide Richtungen
     zugleich. Bei 0 stießen die Phasen hart aneinander.

     ⚠️ Die Gesamtdauer wächst dabei von 640 auf 660ms (+3 %) und bleibt
     damit unter der Wahrnehmungsschwelle. Der Zoom behält 460 seiner 640ms;
     ihn auf 420 zu kürzen hielte die 640ms exakt, nähme der tragenden
     Bewegung aber ein Drittel ihrer Zeit. */
  --wm-zoom-dauer: 460ms;
  --wm-fahrt-dauer: 280ms;
  --wm-ueberlappung: 80ms;
  --wm-fahrt-verzug: calc(var(--wm-zoom-dauer) - var(--wm-ueberlappung));
  --wm-zoom-verzug: calc(var(--wm-fahrt-dauer) - var(--wm-ueberlappung));
  /* Solange die Liste ausfährt, bleibt sie sichtbar — der Wert IST die
     Fahrtdauer, nicht eine zweite Zahl daneben. */
  --wm-sicht-verzug: var(--wm-fahrt-dauer);

  /* Die Explosion setzt ein, wenn der Zoom schon trägt — beide Bewegungen
     überlappen, statt nacheinander abzulaufen.
     ⚠️ SIE IST VON DER PHASENTRENNUNG UNBERÜHRT und behält --dauer-lang:
     Sie lief schon vorher über das Ende des Zooms hinaus (240 + 640 = 880ms)
     und tut es weiter. Genau deshalb ist der Gesamtvorgang nicht länger
     geworden — sein Ende hat immer die Explosion gesetzt, nicht der Zoom. */
  --wm-explosion-verzug: 240ms;
  --wm-hoehe: min(820px, 80vh);
  --wm-luecke: var(--abstand-xl);
  display: flex;
  flex-direction: column;
  gap: var(--wm-luecke);
}

/* ⚠️ UNTEREINANDER DARF DIE LEERE SPALTE KEINE LÜCKE REISSEN. Ein Flex-Kind
   ohne sichtbaren Inhalt ist 0 hoch, sein `gap` ist es nicht — im Gesamtbild
   stünden sonst 32px Leerraum unter der Zeichnung, die niemand erklären
   kann. Ab 1280px gilt das Gegenteil: Dort MUSS die Spalte ihren Platz
   belegen, auch unsichtbar, sonst gäbe es kein Raster, in dem die Zeichnung
   wandern kann. */
@media (max-width: 1279px) {
  .wm-listenspalte:not(:has(> .wm-liste:not([hidden]))) { display: none; }
}

@media (min-width: 1280px) {
  /* ⛔ DIE BREITENGRENZE STEHT AN DER ARENA, NICHT AN DER BÜHNE. Nur so ist
     jede Spalte höchstens so breit, wie die Bühne ohnehin werden darf — und
     erst dadurch füllt die Bühne ihre Spalte immer ganz und beide Karten
     sind gleich breit. Bliebe die Grenze allein an der Bühne, stünde sie auf
     breiten Fenstern schmal in einer breiten Spalte, während die Liste
     daneben die volle Spaltenbreite nähme. */
  .wm-arena {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    /* ⛔ `stretch` ist die Bedingung dafür, dass die Liste die Höhe der
       Zeichnung ÜBERHAUPT bekommt — dieselbe Bauart wie bei `.wx-raster`.
       Der Unterschied dort: Die Liste darf die Zeile mitsetzen. Hier darf
       sie es nicht, und das erzwingt die absolute Karte weiter unten. */
    align-items: stretch;
    /* ⛔ `width: 100%` GEHÖRT DAZU, und ohne sie bleibt von der Arena nichts
       übrig. Sie ist ein Flex-Kind von `.wm-modell`; ein `margin: auto` hebt
       dort das Dehnen auf, das Element fällt auf seine Inhaltsbreite zurück
       — und die ist bei zwei `minmax(0, 1fr)`-Spalten genau die Lücke.
       Gemessen: Arena 24px breit, beide Spalten 0, Bühne 0 × 0. Ein stiller
       Fehler, wie er im Buche steht: Die Rechnung war richtig, `max-width`
       stand auf 1104px, und zu sehen war nichts. */
    width: 100%;
    max-width: calc(var(--wm-hoehe) * 3 / 4 * 2 + var(--wm-luecke));
    margin-inline: auto;
  }

  /* ⛔ IM GESAMTBILD STEHT DIE ZEICHNUNG MITTIG IN DER GANZEN ARENA. Die
     Strecke dorthin ist die halbe Spaltenbreite plus die halbe Lücke — als
     Prozentwert bezieht sich `50%` auf die Breite des Elements selbst, also
     genau auf die Spalte. Damit stimmt die Rechnung auf jeder Fensterbreite,
     ohne dass irgendwo gemessen würde.
     ⚠️ Gelesen wird der Zustand der BÜHNE über `:has()` — er steht dort
     schon, und ein zweites Attribut an der Arena wäre ein zweiter Ort für
     denselben Zustand. Dieselbe Entscheidung wie bei den Verfahrenskarten. */
  /* ⛔ HIER LIEGT DER VERZUG GENAU ANDERSHERUM ALS BEIM ZOOM. Diese Regel
     beschreibt den OFFENEN Zustand (translateX 0), gilt also beim Öffnen —
     und dort ist die Fahrt die zweite Phase und wartet auf den Zoom. */
  .wm-bildspalte {
    transition: transform var(--wm-fahrt-dauer) var(--wm-kurve)
                var(--wm-fahrt-verzug);
  }
  .wm-arena:has(.wm-buehne[data-wm-ansicht="gesamt"]) .wm-bildspalte {
    transform: translateX(calc(50% + var(--wm-luecke) / 2));
    /* Rückwärts fährt sie zuerst — ohne Verzug. */
    transition: transform var(--wm-fahrt-dauer) var(--wm-kurve);
  }

  /* ⛔ DIE KARTE LIEGT ABSOLUT, UND DAS IST DIE GANZE HÖHENZUSAGE. Ein
     absolut liegendes Kind trägt zur Höhe seines Elternteils nichts bei —
     die Rasterzeile hängt damit allein an der Bildspalte, gleich wie lang
     die Liste ist, und `inset: 0` gibt ihr genau diese Höhe zurück. Ohne das
     wüchse die Zeile am längeren der beiden Kinder, und „nie höher als die
     Zeichnung" wäre eine Bitte statt einer Zusage.
     Dasselbe Mittel wie beim Detail in `.wx-buehne` und bei den Blättern. */
  .wm-listenspalte { position: relative; }
  .wm-liste { position: absolute; inset: 0; }
  .wm-liste > .wx-stueckliste { height: 100%; }

  /* Die Spalte belegt ihren Platz auch unsichtbar — sonst gäbe es kein
     Raster, in dem die Zeichnung nach links wandern könnte.
     ⚠️ `visibility` nimmt ihr zugleich den Tabulatorweg; die Verzögerung
     gehört zum AUSBLENDEN und nur dorthin, damit sie beim Zufahren sichtbar
     bleibt, bis die Zeichnung wieder mittig steht. Dieselbe Bauart wie beim
     Fenster der Klappbereiche. */
  .wm-listenspalte {
    opacity: 1;
    visibility: visible;
    /* Sie blendet MIT der Fahrt auf, also mit demselben Verzug und derselben
       Dauer — sonst wären es zwei Bewegungen in der zweiten Phase. */
    transition: opacity var(--wm-fahrt-dauer) var(--wm-kurve)
                var(--wm-fahrt-verzug),
                visibility 0s;
  }
  .wm-arena:has(.wm-buehne[data-wm-ansicht="gesamt"]) .wm-listenspalte {
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--wm-fahrt-dauer) var(--wm-kurve),
                visibility 0s linear var(--wm-sicht-verzug);
  }
}

/* ⛔ HIER BRAUCHT ES DOCH EINE EIGENE REGEL, und das ist die Ausnahme von
   der Projektregel: Der Block in basis.css setzt `transition-duration`,
   `animation-duration` und `animation-iteration-count` — die VERZÖGERUNG
   fasst er nicht an. Gemessen blieb sie bei abbestellter Bewegung stehen:
   Die Zeichnung stünde sofort am Ziel, und die Liste erschiene erst eine
   halbe Sekunde später. Genau der Nachlauf, den diese Vorliebe abbestellt —
   nur ohne die Animation, die ihn erklären würde.

   ⛔ GENULLT WERDEN DIE TOKENS, NICHT DIE EINZELNEN REGELN. Seit die
   Bewegung in zwei Phasen läuft, hängen VIER Verzögerungen daran (Zoom,
   Fahrt, Sichtbarkeit, Explosion) in acht Regeln. Eine Aufzählung von
   Selektoren wäre die erste Stelle, an der beim nächsten Umbau eine
   vergessen wird — und eine vergessene Verzögerung sieht man nur mit
   abbestellter Bewegung, also fast nie. Über die Tokens gilt es für jede
   Regel, die sie liest, auch für die nächste.

   ⚠️ Dasselbe Muster steht bei `.wk-fenster` (Klappbereiche der Seite) und
   dort mit demselben Mangel. Er ist gemeldet und hier bewusst NICHT
   mitbehoben: Das wäre eine Änderung an einem Baustein, der zu diesem
   Auftrag nicht gehört. */
@media (prefers-reduced-motion: reduce) {
  .wm-arena {
    --wm-zoom-verzug: 0s;
    --wm-fahrt-verzug: 0s;
    --wm-sicht-verzug: 0s;
    --wm-explosion-verzug: 0s;
  }
}

/* ⛔ DIE BÜHNE IST HOCHFORMATIG (1080 × 1440) — über die volle Inhaltsbreite
   gestreckt wäre sie fast 1500 px hoch und damit unbrauchbar. Sie richtet
   sich deshalb nach der HÖHE und zentriert sich in ihrem Block: Der Block
   steht über die volle Breite, die Zeichnung darin so groß, wie sie auf einen
   Bildschirm passt. `80vh` hält sie auch auf flachen Fenstern ganz im Bild. */
.wm-buehne {
  container-type: inline-size;
  position: relative;
  width: min(100%, calc(var(--wm-hoehe) * 3 / 4));
  aspect-ratio: 1080 / 1440;
  margin-inline: auto;
  overflow: hidden;
  border-radius: var(--radius-m);
  background: var(--schema-feld);
}

/* Der Koordinatenraum aller vier Blätter: fest 1080 × 1440, als Ganzes auf
   die Bühnenbreite skaliert — dieselbe Bauart und dieselbe Rechnung wie
   .wx-blatt, samt der beiden Schreibweisen für ältere Browser.
   ⛔ `scale()` NIMMT EINE ZAHL, KEINE LÄNGE: `calc(100cqw / 1080)` wäre eine
   Länge, die Deklaration ungültig, der Faktor bliebe 1 und die Zeichnung
   ragte weit aus ihrem Feld — verschluckt von `overflow: hidden`. */
.wm-raum {
  position: absolute;
  left: 0;
  top: 0;
  width: 1080px;
  height: 1440px;
  transform-origin: 0 0;
  transform: scale(tan(atan2(100cqw, 1080px)));
  transform: scale(calc(100cqw / 1080px));
}

/* ⛔ JEDES BLATT IST SEIN EIGENER CONTAINER, und nur deshalb darf darin
   dieselbe .wx-blatt-Bauart stehen wie in den beiden bestehenden
   Zeichnungen: Jene skaliert sich selbst über `scale(100cqw / breite)`; ist
   der Container exakt so breit wie das Blatt, ergibt die Rechnung genau 1
   und die Eigenskalierung wird von selbst neutral. Die Vergrößerung trägt
   dann allein die Bühne. */
.wm-blatt {
  position: absolute;
  container-type: inline-size;
  transform-origin: 50% 50%;
}

/* ⛔ DER ZOOM STEHT FÜR ALLE BLÄTTER AN EINER STELLE, UND SEIN VERZUG HÄNGT
   AM ZUSTAND DER BÜHNE — nicht am einzelnen Blatt. Nur so tragen Ausschnitt
   und Detail in JEDER Richtung dieselbe Verzögerung, und die Kongruenz ist
   baulich gesichert statt an zwei Stellen wiederholt.

   ⛔ EIN ERSTER VERSUCH HING SIE AN `[data-wm-aktiv]` UND WAR GEMESSEN
   FALSCH: Dieses Attribut heißt „dieses Blatt ist das gerade gezeigte" — im
   Gesamtzustand trägt es die GESAMTANSICHT. Beim Schließen bekam sie damit
   den Verzug 0 und das Detail 200ms, gemessen `Blatt gesamt 0…460` gegen
   `Blatt a 200…660`. Die beiden liefen 200ms lang auseinander; am
   Bildschirm sähe man ein Überblenden statt eines Zooms, und keine der
   beiden Regeln wäre für sich falsch gewesen.

   Zielzustand „gesamt" = der Weg zurück, dort ist der Zoom die ZWEITE Phase
   und wartet, bis die Zeichnung wieder mittig steht. */
.wm-buehne .wm-blatt {
  transition: transform var(--wm-zoom-dauer) var(--wm-kurve) var(--wm-zoom-verzug),
              opacity var(--dauer-mittel) ease var(--wm-zoom-verzug);
}
/* Zielzustand „Detail" = der Weg hinein, dort ist der Zoom die ERSTE Phase
   und beginnt sofort. */
.wm-buehne:not([data-wm-ansicht="gesamt"]) .wm-blatt {
  transition: transform var(--wm-zoom-dauer) var(--wm-kurve),
              opacity var(--dauer-mittel) ease;
}

/* ⛔ WEDER `display: none` NOCH `visibility: hidden` FÜR DAS ZURÜCKTRETENDE
   BLATT — beides ist nicht animierbar, und die ganze Bewegung lebt davon,
   dass beide Blätter währenddessen sichtbar sind. Was ein unsichtbares Blatt
   nicht tun darf, ist Klicks abfangen: dafür `pointer-events`. Fokussierbar
   ist darin nichts, die Zeichnungen tragen keine Bedienelemente außer den
   Marken der Gesamtansicht — und die liegen im Blatt, das gerade oben ist. */
.wm-blatt:not(.wm-blatt--gesamt) {
  opacity: 0;
  pointer-events: none;
  transform: translate(var(--wm-tx), var(--wm-ty))
             scale(calc(1 / var(--wm-eigen-grad)));
}
.wm-blatt[data-wm-aktiv] {
  opacity: 1;
  pointer-events: auto;
  transform: none;
}

/* Die Gesamtansicht zoomt um den angetippten Punkt hinein und tritt zurück.
   ⛔ IHRE BEWEGUNG IST DIE UMKEHRUNG DER DES DETAILS, und bei gleicher
   Zeitkurve sind beide kongruent: mit g(t) = 1 + t(grad−1) und
   d(t) = 1/grad + t(1 − 1/grad) gilt d(t) = g(t)/grad für jedes t. Beide
   zeigen also in JEDEM Augenblick denselben Ausschnitt in derselben
   Vergrößerung. Wer einer der beiden Regeln eine andere Kurve oder Dauer
   gibt, macht aus dem Zoom eine Kreuzblende.

   ⛔ UND DAS GILT SEIT DER PHASENTRENNUNG AUCH FÜR DEN VERZUG: Beide Blätter
   müssen in jeder Richtung dieselbe Verzögerung tragen. Bekäme eines von
   beiden seine früher als das andere, liefen Ausschnitt und Detail für die
   Dauer des Unterschieds auseinander — und niemandem fiele auf, warum. */
.wm-blatt--gesamt {
  transform-origin: var(--wm-zoom-x, 540px) var(--wm-zoom-y, 720px);
}
.wm-buehne:not([data-wm-ansicht="gesamt"]) .wm-blatt--gesamt {
  opacity: 0;
  pointer-events: none;
  transform: scale(var(--wm-grad, 1));
}

/* ---------- Die Explosion von Detail A ---------- */

/* ⛔ DIE ZEICHNUNG ZEIGT DEN EXPLODIERTEN ZUSTAND — das ist ihr Ruhezustand
   und der Zustand, in dem sie seit dem 05.09.2026 auf dieser Seite steht.
   Zusammengebaut ist der ABWEICHENDE Zustand, und deshalb trägt ihn die
   Grundregel: Jedes der vierzehn Bauteile wird um die Strecke zurück-
   geschoben, die es in der Explosion zurückgelegt hat (--dx/--dy, im Markup
   je Gruppe). Beim Öffnen läuft es auf null, und die Zeichnung fährt vor den
   Augen des Betrachters auseinander.
   ⚠️ Die Gruppe ist 0 × 0 groß und liegt bei (0, 0). Das ist Pflicht: Ein
   Element mit `transform` wird zum Bezugsrahmen für seine absolut liegenden
   Kinder, und die tragen ihre Blattkoordinaten. Bei jeder anderen Lage oder
   Größe verschöbe sich die ganze Gruppe gegen die Zeichnung. */
.wx-teil {
  position: absolute;
  left: 0;
  top: 0;
  width: 0;
  height: 0;
  transform: translate(var(--dx, 0px), var(--dy, 0px));
  transition: transform var(--dauer-lang) var(--wm-kurve);
}
/* ⛔ DIE EXPLOSION SETZT VERZÖGERT EIN, das Zusammenfahren nicht. Der Zoom
   führt hinein, und erst wenn er trägt, geht das Bauteil auseinander — beide
   Bewegungen überlappen, statt nacheinander abzulaufen. Rückwärts wäre eine
   Verzögerung falsch: Dort soll das Bauteil zusammen sein, BEVOR die Ansicht
   herauszieht, sonst führe man aus einer noch offenen Explosion heraus. */
.wm-blatt[data-wm-aktiv] .wx-teil {
  transform: none;
  transition-delay: var(--wm-explosion-verzug);
}
/* Die Nummernfahnen gehören zur Explosionsdarstellung: Sie zeigen auf
   Bauteile an ihrem auseinandergefahrenen Platz. Am zusammengebauten Modell
   zeigten sie ins Leere, deshalb kommen sie mit der Bewegung. */
.wx-marken {
  opacity: 0;
  transition: opacity var(--dauer-mittel) ease;
}
.wm-blatt[data-wm-aktiv] .wx-marken {
  opacity: 1;
  transition-delay: var(--wm-explosion-verzug);
}

/* ---------- Die Marken der Gesamtansicht ---------- */

/* ⛔ DER GANZE AUSSCHNITT IST DER KNOPF, nicht nur die Lupe: Ein 64-px-Ziel
   in einer 300 px breiten Markierung wäre auf dem Handy ein Nadelöhr.
   ⚠️ KEINE PULSATION. Die Vorlage lässt Rahmen und Lupe dauerhaft atmen;
   auf einer Seite, die man liest, zöge das den Blick ununterbrochen auf drei
   Stellen zugleich. Der gestrichelte Rahmen und die Lupe sagen auch ruhend,
   dass hier etwas zu holen ist — und beim Überfahren wird es deutlich. */
.wm-marke {
  position: absolute;
  padding: 0;
  border: 2px dashed var(--schema-kontur);
  background: var(--wm-marke-feld);
  cursor: zoom-in;
  transition: background var(--dauer-kurz) ease,
              border-color var(--dauer-kurz) ease;
}
.wm-marke--rund { border-radius: 50%; }
.wm-marke:hover, .wm-marke:focus-visible {
  background: var(--wm-marke-feld-aktiv);
  border-color: var(--schema-marke);
}
/* Der Fokusring steht AUSSERHALB des gestrichelten Rahmens, sonst läge er
   auf ihm und wäre von ihm nicht zu unterscheiden. */
.wm-marke:focus-visible {
  outline: 3px solid var(--schema-marke);
  outline-offset: 3px;
}
/* Die Lupe sagt, was der Klick tut. Sie trägt `pointer-events: none`, damit
   der Zeiger immer den Knopf trifft und nie das Symbol darin. */
.wm-lupe {
  position: absolute;
  width: 64px;
  height: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 3px solid var(--schema-kontur);
  background: var(--wm-lupe-glas);
  pointer-events: none;
}
/* Der Stiel der Lupe. */
.wm-lupe::after {
  content: "";
  position: absolute;
  left: 39px;
  top: 39px;
  width: 30px;
  height: 9px;
  box-sizing: border-box;
  border: 2px solid var(--schema-kontur);
  border-radius: 1px;
  transform: rotate(45deg);
  transform-origin: 0 50%;
  background: var(--wm-lupe-stiel);
}
/* Der Buchstabe ist eine Kennung, also Schreibmaschinenschrift — dieselbe
   Regel wie bei den Positionsnummern der Stücklisten. */
.wm-lupe-buchstabe {
  font-family: var(--schrift-mono);
  font-weight: 600;
  font-size: 28px;
  line-height: 1;
  color: var(--schema-kontur);
}
.wm-fahne {
  position: absolute;
  display: flex;
  flex-direction: column;
  gap: 2px;
  pointer-events: none;
  color: var(--schema-kontur);
}
.wm-fahne--rechts { width: 190px; text-align: left; }
.wm-fahne--links { width: 170px; text-align: right; }
.wm-fahne-nr {
  font-family: var(--schrift-mono);
  font-weight: 600;
  font-size: 29px;
  line-height: 31px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
.wm-fahne-titel {
  font-family: var(--schrift-mono);
  font-weight: 500;
  font-size: 21px;
  line-height: 24px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--schema-marke);
}
.wm-linie {
  position: absolute;
  height: 1px;
  background: var(--schema-kontur);
  opacity: 0.55;
  pointer-events: none;
}
/* Marken, Fahnen und Linien gehören zur Gesamtansicht. Sie treten mit ihr
   zurück, statt über dem gezoomten Bild stehenzubleiben. */
.wm-buehne:not([data-wm-ansicht="gesamt"]) .wm-marke,
.wm-buehne:not([data-wm-ansicht="gesamt"]) .wm-fahne,
.wm-buehne:not([data-wm-ansicht="gesamt"]) .wm-linie { opacity: 0; }

/* Die Beschriftungen IN den Zeichnungen sind BENANNT, nicht abgelesen — also
   Grotesk und nicht Mono. Das ist dieselbe Regel wie bei .wx-nr und
   .wx-mass, nur ihre andere Hälfte. */
/* ⛔ DIE BESCHRIFTUNGEN BRECHEN NICHT UM, sie sind verankert. Die Vorlage
   setzt sie in eine KONDENSIERTE Schrift (Barlow Condensed) und gibt ihnen
   feste Kastenbreiten; unsere Grotesk braucht dieselben Wörter breiter —
   gemessen 14 von 34, „Elektronik" allein 156 px in einem 90-px-Kasten.
   Zusammen mit `overflow-wrap: break-word` aus basis.css (dem Auffangnetz
   für Freitext) ergab das Trennungen mitten im Wort: „ELEKT RONIK".
   Die Kästen tragen deshalb `width: max-content` und sind an der Seite
   verankert, zu der sie zeigen — linksstehende über `right`, damit sie nach
   LINKS wachsen und ihre Führungslinie treffen. Ein einzeiliger Text kann so
   nicht mehr umbrechen, und die Schrift bleibt in Lesegröße.
   ⚠️ Die sechs ABSICHTLICH zweizeiligen Beschriftungen („Kabelabgang /
   mit Zugentlastung") behalten ihre Kastenbreite — sie tragen zwei
   Kindelemente und sollen umbrechen. */
.wx-txt {
  font-family: var(--schrift);
  overflow-wrap: normal;
}

/* ---------- Die Stückliste des geöffneten Details ---------- */

/* Im Gesamtbild gibt es keine Positionen zu zeigen; sie erscheint mit dem
   Detail, zu dem sie gehört. Ab 1280px steht sie rechts neben der Zeichnung
   und übernimmt deren Höhe (siehe Arena), darunter unter ihr über die volle
   Breite.

   ⛔ UNTEREINANDER GIBT ES KEINE HÖHENGRENZE UND KEIN INNENROLLEN, und das
   ist eine Entscheidung: Dort hat die Liste keine Nachbarin, an der sie sich
   messen müsste. Eine Deckelung auf die Zeichnungshöhe hieße, in einer Karte
   zu rollen, während darunter die ganze Seite frei ist. Sie wächst nach
   unten — das Ausklappen einer Position gilt trotzdem, es ist eine Sache der
   Liste und nicht des Layouts. */
.wm-liste { display: block; }
.wm-liste[hidden] { display: none; }

/* ---------- Die sechs Baugruppen ---------- */

.wg-block { display: flex; flex-direction: column; gap: var(--abstand-m); }
.wg-titel {
  margin: 0;
  font-family: var(--schrift-serif);
  font-size: var(--headline-small);
  font-weight: 600;
  color: var(--on-surface);
}
.wg-liste {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: var(--abstand-l);
}
.wg-zeile {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-xxs);
  min-width: 0;
  padding: var(--abstand-l);
  border-radius: var(--radius-l);
  background: var(--surface-container);
  box-shadow: var(--karten-kante);
}
.wg-name {
  margin: 0;
  font-size: var(--title-medium);
  font-weight: 500;
  color: var(--on-surface);
}
/* Die Zeilen des Resin-Schichtschnitts. Bis zum 06.09.2026 trug sie auch die
   Bauteilliste des FDM-Schemas; die ist mit der Schemazeichnung entfallen,
   die Klassen sind geblieben, weil der Resin-Teil unberührt bleibt. */
.wf-inhalt { display: flex; flex-direction: column; gap: var(--abstand-xxs); min-width: 0; }
.wf-name {
  margin: 0;
  font-size: var(--title-medium);
  font-weight: 500;
  color: var(--on-surface);
}
.wf-satz { margin: 0; font-size: var(--body-medium); color: var(--on-surface-variant); }

/* Die Nummer trägt in Liste und Schichtschnitt dieselbe Form. */
.wn-nummer {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-pille);
  background: var(--primary-container);
  color: var(--on-primary-container);
  font-family: var(--schrift-mono);
  font-size: var(--label-medium);
  font-weight: 500;
}
.wn-nummer--klein { width: 28px; height: 28px; font-size: var(--label-small); }

/* ---------- Hotend ----------

   ⛔ DER ABSCHNITT IST LEER — UND ZWAR ABSICHTLICH (06.09.2026). Hier stand
   der Balkenstapel „Hotend von oben nach unten" (`.wh-raster`, `.wh-karte`,
   `.wh-inhalt`, `.wh-stapel`, `.wh-balken` samt fünf Stufen und zwei
   Tinten, `.wh-name`, `.wh-wert`, `.wh-satz`, `.wh-bild`). Er nannte fünf
   Bauteile; die Explosionszeichnung darunter führt dieselben fünf plus
   Lüfter, Thermistor und Silikonsocke, jedes nummeriert und erklärt — der
   Stapel war damit eine zweite, gröbere Fassung derselben Aussage.

   ⚠️ DIE FARBROLLEN `--hotend-1…5` UND DIE BEIDEN TINTEN BLEIBEN in
   css/theme.css und im Druckblock von css/basis.css stehen (Entscheidung vom
   06.09.2026). Sie haben derzeit keinen Leser; wer sie verändert, ändert
   nichts Sichtbares. Ebenso bleibt `BILDER.hotend` in js/seite-wissen.js —
   als Notiz, nicht als Rückweg: Die Karte, in die ein Foto käme, gibt es
   nicht mehr. */

/* ---------- Druckkopf: Explosionszeichnung und Stückliste ----------

   Der zweite, tiefere Blick auf den Druckkopf. Links die Zeichnung
   (js/wissen-explosion.js), rechts die Stückliste mit 14 Positionen. */

.wx-raster {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--abstand-xl);
  /* ⛔ BEIDE SPALTEN TEILEN SICH EINE ZEILENHÖHE (06.09.2026). Vorher stand
     hier `start`: Jede Karte war so hoch wie ihr Inhalt, und die Stückliste
     traf die Zeichnung nur näherungsweise — gemessen von −17,5px (1600) bis
     +104,6px (900). Mit `stretch` ist die Zeilenhöhe das Maximum aus beiden,
     und beide Karten schließen unten bündig ab, ohne dass irgendwo eine
     Pixelzahl steht.

     ⚠️ WER VON BEIDEN DIE ZEILE SETZT, WECHSELT MIT DER BREITE, und das muss
     man wissen: Ab rund 1015px ist die Zeichnung höher (sie wächst mit
     1,846 × Spaltenbreite), die Liste zieht dann nach und verteilt den
     Zuwachs auf ihre 14 Zeilen. Darunter setzt die LISTE die Zeile, weil
     ihre Positionen die Trefferfläche von 40px nicht unterschreiten dürfen;
     dann bleibt unter der Zeichnung Fläche stehen. Das ist der Preis, und er
     ist der kleinere: Vorher stand derselbe Betrag als Überstand der Karte
     unter der Zeichnung. */
  align-items: stretch;
}

/* ⛔ JEDES BLATT NENNT SEIN EIGENES MASS (06.09.2026). Seit dem Harzdrucker
   gibt es zwei Zeichnungen mit verschiedenen Formaten, und die Maße stehen
   an drei Stellen zugleich: im Seitenverhältnis der Bildbox, in der Breite
   der Bühne und im Nenner ihrer Skalierung. Als Zahl hätte man sie dreimal
   nachzuziehen — hier steht sie einmal.

   ⚠️ UNITLOS, und das ist Absicht: `aspect-ratio` nimmt Zahlen, keine
   Längen. Die Pixel kommen erst dort dazu, wo sie gebraucht werden
   (`calc(var(--wx-blatt-breite) * 1px)`).

   ⛔ KEINES DER BEIDEN IST DIE VORGABE. Beide Raster nennen ihr Maß
   ausdrücklich; ein stillschweigend geerbter Wert wäre beim nächsten Blatt
   die Falle, die niemand sieht — die Zeichnung stünde dann schlicht im
   falschen Verhältnis, und `overflow: hidden` verschluckte den Rest. */
.wx-raster--druckkopf { --wx-blatt-breite: 780; }
.wx-raster--harzwerk  { --wx-blatt-breite: 720; }
/* Beide Blätter sind gleich hoch — die Zahl steht trotzdem als Token da,
   damit das Verhältnis als Rechnung lesbar bleibt und nicht als Literal in
   der `aspect-ratio` verschwindet. */
.wx-raster { --wx-blatt-hoehe: 1440; }

/* ⛔ DAS BILDFELD IST IN BEIDEN LAGEN HELL, wie das der Schemazeichnung
   daneben. Die Zeichnung bringt ihren eigenen hellen Verlauf mit; ohne ein
   helles Feld darunter schnitte sie sich als heller Block ins dunkle Layout.
   Sie nimmt dieselbe Rolle wie .wm-buehne (--schema-feld) — eine zweite,
   eigene Farbe wäre ein zweiter Wert für dieselbe Aussage, und die beiden
   Felder stünden im selben Abschnitt sichtbar nebeneinander. */
.wx-bildkarte {
  padding: var(--abstand-l);
  border-radius: var(--radius-l);
  background: var(--surface-container);
  box-shadow: var(--karten-kante);
}

/* ⛔ DIE BILDBOX TRÄGT DAS SEITENVERHÄLTNIS DER BÜHNE (780 × 1440). Sie ist
   der Bezug, gegen den skaliert wird: `container-type: inline-size` macht
   ihre Breite zur Einheit cqw, und das Blatt darin rechnet daraus seinen
   Faktor. Ein anderes Verhältnis ließe unten Fläche stehen oder schnitte
   die Silikonsocke ab — beides fiele erst am schmalen Fenster auf.
   ⚠️ Das Polster gehört an die KARTE darum herum, nicht hierher: Am selben
   Element ginge es in die Breite ein, gegen die skaliert wird, und die
   Zeichnung stünde um seinen Betrag zu klein in ihrem Feld. */
.wx-bild {
  container-type: inline-size;
  position: relative;
  aspect-ratio: var(--wx-blatt-breite) / var(--wx-blatt-hoehe);
  overflow: hidden;
  border-radius: var(--radius-m);
  background: var(--schema-feld);
}

/* ⛔ DIE BÜHNE IST FEST 780 × 1440 UND WIRD ALS GANZES SKALIERT. Die 229
   Kästen der Zeichnung tragen ihre Maße als Inline-Stil in Pixeln; sie
   fließen nicht mit. Ein `transform: scale` ist der einzige Weg, der alle
   zugleich trifft — und er kostet weder Layout noch eine Zahl in
   JavaScript, weil `cqw` die Containerbreite selbst liefert.
   ⚠️ `transform-origin: 0 0` ist Pflicht: Mit dem Vorgabewert (Mitte) zöge
   sich das Blatt beim Verkleinern zur Feldmitte zusammen und ließe oben und
   links einen Rand stehen, der mit der Breite wanderte. */
.wx-blatt {
  position: absolute;
  left: 0;
  top: 0;
  width: calc(var(--wx-blatt-breite) * 1px);
  height: calc(var(--wx-blatt-hoehe) * 1px);
  transform-origin: 0 0;
  /* ⛔ `scale()` NIMMT EINE ZAHL, KEINE LÄNGE — und `calc(100cqw / 780)` ist
     eine Länge. Diese Fassung stand hier zuerst, war damit ungültig und
     wurde verworfen: Der Faktor blieb 1, die Zeichnung ragte gemessen bis
     zu 995px aus ihrem Feld und wurde von `overflow: hidden` verschluckt.
     Ein stiller Fehler, wie er im Buche steht — am Bildschirm sah man ein
     angeschnittenes Bild und nirgends eine Meldung.
     Richtig ist die Division durch eine LÄNGE (780px), denn Länge geteilt
     durch Länge ergibt eine Zahl. Zwei Schreibweisen derselben Rechnung:
     erst der trigonometrische Umweg für ältere Browser, dann die lesbare
     Fassung, die ihn überschreibt, wo sie gilt. Beide gemessen. */
  transform: scale(tan(atan2(100cqw, calc(var(--wx-blatt-breite) * 1px))));
  transform: scale(calc(100cqw / (var(--wx-blatt-breite) * 1px)));
  /* ⛔ Beide Werte stehen als Rollen in theme.css, nicht hier: In diesem Block
     dürfen keine Farbliterale stehen, und ein Prüffall hält das fest. Sie
     sind schemaunabhängig wie die --schema-*-Rollen — die Zeichnung ist ein
     helles technisches Blatt und bleibt es. */
  color: var(--wx-blatt-tinte);
  background: var(--wx-blatt-grund);
  overflow: hidden;
}

/* Kann der Browser den Faktor auf keinem der beiden Wege ausrechnen, bliebe
   ein Ausschnitt der linken oberen Ecke stehen — schlimmer als kein Bild.
   Dann entfällt die Zeichnung, und die Stückliste nimmt die volle Breite:
   Sie trägt alle vierzehn Positionen samt Beschreibung ohnehin als Text.
   Dasselbe Muster wie bei der Schemazeichnung ohne Maskenunterstützung. */
/* ⚠️ HIER STEHT DIE ZAHL WEITER ALS LITERAL, und das ist richtig:
   `@supports` prüft eine FÄHIGKEIT — ob Länge durch Länge eine Zahl ergibt —,
   nicht einen Wert. Ein `var()` wäre an dieser Stelle nicht auflösbar; die
   780 sind hier ein beliebiges Beispielmaß und kein Blattformat. */
@supports not ((transform: scale(calc(100cqw / 780px))) or
               (transform: scale(tan(atan2(100cqw, 780px))))) {
  .wx-bildkarte { display: none; }
  .wx-raster { grid-template-columns: minmax(0, 1fr); }
}

/* ⛔ DIE MASSANGABEN IM BLATT NEHMEN DIESELBE MONO (06.09.2026). Das
   Harzdrucker-Blatt trägt zwei davon — „0,05 mm" an der Bauteilschicht und
   „0,15 mm" an der FEP-Folie —, beide standen in der Vorlage in 'Barlow
   Condensed'. Dieselbe Regel und derselbe Grund wie bei den Nummernkreisen:
   Die Familie liefert die Seite nicht aus, und ein Maß ist ein abgelesener
   Wert. ⚠️ Größe, Laufweite und Farbe bringt das Blatt selbst mit; hier
   steht nur die Familie, damit die Zeichnung im Übrigen unverändert bleibt.
   Das FDM-Blatt kennt diesen Fall nicht, es führt keine Maßangaben. */
.wx-mass { font-family: var(--schrift-mono); }

/* ⛔ DIE NUMMERNKREISE NEHMEN --schrift-mono. In der Vorlage stand hier
   'Barlow Condensed'; die Familie liefert die Seite nicht aus und darf sie
   nicht nachladen (CSP default-src 'self'). Mono ist zugleich die richtige
   Wahl: Eine Positionsnummer ist ein abgelesener Wert.
   ⚠️ 19px statt der 22px der Vorlage — gemessen: Die Ziffern der Mono sind
   breiter, und „14" berührte im 34px-Kreis (30px innen) sonst dessen Rand.
   Wer die Familie wechselt, misst die zweistelligen Nummern nach. */
.wx-nr {
  font-family: var(--schrift-mono);
  font-weight: 600;
  font-size: 19px;
  line-height: 1;
}

/* ⛔ DIE ZEILENHÖHE WIRD NICHT MEHR GERECHNET, SIE FÜLLT (06.09.2026).
   Bis dahin stand hier `--wx-zeile: clamp(40px, 10.6cqw, 78px)` — eine aus
   der Bildhöhe hergeleitete NÄHERUNG (Zeile = (1,846·C − 168) / 15,625,
   über den Bereich fast konstant 10,5–10,7cqw). Sie traf 99–101 % und
   konnte gar nichts anderes: Ein Faktor kann die Höhe der Nachbarspalte
   annähern, aber nie treffen, weil Polster, Kopfzeile und der umbrechende
   Einleitungssatz mit der Breite anders wachsen als das Bild.

   Jetzt trägt das Raster EINE Zeilenhöhe (`align-items: stretch`, siehe
   `.wx-raster`), die Karte ist so hoch wie die Zeichnung, die Bühne nimmt
   den Rest und die 14 Positionen verteilen ihn unter sich. Das trifft nicht
   annähernd, sondern exakt — und ohne eine einzige Zahl.

   ⛔ `--wx-zeile` IST SEITHER NUR NOCH DIE UNTERGRENZE, und deshalb steht
   sie nicht mehr in der 900px-Abfrage: Eine dort erhöhte Untergrenze hübe
   die Liste über die Zeichnung, und dann setzte wieder die Liste die Zeile
   — genau die Näherung, die abgeschafft ist. Nachgemessen liefe die Liste
   mit dem alten `clamp(…, 78px)` bei 1600px auf rund 1422px gegen 1287px
   Zeichnung.

   ⚠️ DIE ÜBRIGEN TOKENS BLEIBEN cqw-GEBUNDEN. Nummernkreis und Schrift
   müssen mit der Zeile wachsen, und die Zeile wächst mit der Bildhöhe, also
   mit der Spaltenbreite — dieselbe Größe, an der sie schon hingen. Wer sie
   auf feste Maße stellt, bekommt einen 28px-Kreis in einer 67px-Zeile.

   ⚠️ EIN ELEMENT IST NICHT SEIN EIGENER CONTAINER. Die Tokens stehen
   trotzdem hier: Ein nicht registriertes Custom Property wird als Text
   weitergereicht und erst BEIM LESER ausgewertet — `cqw` löst also im
   Kind gegen diesen Container auf, nicht hier gegen einen äußeren. Polster
   und Lücke der Stückliste SELBST müssen deshalb feste Maße bleiben.
   Dieselbe Falle wie beim Bildsaum der Startseite (14.08.2026). */
.wx-stueckliste {
  container-type: inline-size;
  --wx-zeile: 40px;
  --wx-luft: var(--abstand-s);
  --wx-luecke: var(--abstand-xxs);
  --wx-nummer: 28px;
  --wx-nummer-schrift: var(--label-small);
  --wx-name: var(--body-medium);
  --wx-titel: var(--label-medium);
  --wx-zahl: var(--label-small);
  --wx-satz: var(--body-medium);
  --wx-detail-name: var(--title-large);
  --wx-detail-kurz: var(--body-large);
  --wx-detail-lang: var(--body-medium);
  display: flex;
  flex-direction: column;
  gap: var(--abstand-m);
  padding: var(--abstand-l);
  border-radius: var(--radius-l);
  background: var(--surface-container);
  box-shadow: var(--karten-kante);
}

/* Nur zweispaltig: Nummernkreis und Schrift wachsen mit der Spalte, damit
   sie zu den Zeilen passen, die die geteilte Zeilenhöhe erzeugt.
   ⛔ `--wx-zeile` GEHÖRT NICHT MEHR HIERHER — sie ist die Untergrenze, und
   eine erhöhte Untergrenze machte die Liste wieder höher als die Zeichnung
   (siehe den Kopf dieses Abschnitts). */
@media (min-width: 900px) {
  .wx-stueckliste {
    --wx-luft: clamp(8px, 1.8cqw, 17px);
    --wx-luecke: clamp(2px, 1.33cqw, 10px);
    --wx-nummer: clamp(28px, 6.3cqw, 42px);
    --wx-nummer-schrift: clamp(12px, 2.7cqw, 17px);
    --wx-name: clamp(14px, 3.1cqw, 19px);
    --wx-titel: clamp(12px, 2.3cqw, 15px);
    --wx-zahl: clamp(11px, 2.1cqw, 14px);
    --wx-satz: clamp(14px, 2.6cqw, 17px);
    --wx-detail-name: clamp(20px, 4.4cqw, 29px);
    --wx-detail-kurz: clamp(15px, 3.1cqw, 20px);
    --wx-detail-lang: clamp(14px, 2.8cqw, 18px);
  }
}

/* Titel links, Zahl rechts — dieselbe Aufteilung wie der Kopf der
   Filterspalte. Beide in Versalien-Mono: Der Titel benennt, die Zahl ist
   abgezählt. */
.wx-kopf {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--abstand-s);
  padding-bottom: var(--abstand-s);
  border-bottom: 1px solid var(--outline-variant);
}
.wx-kopf-titel {
  font-family: var(--schrift-mono);
  font-size: var(--wx-titel);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--on-surface);
}
.wx-kopf-zahl {
  font-family: var(--schrift-mono);
  font-size: var(--wx-zahl);
  color: var(--on-surface-variant);
}
.wx-satz {
  margin: 0;
  font-size: var(--wx-satz);
  color: var(--on-surface-variant);
}

/* ⛔ DIE BÜHNE IST DER RESERVIERTE PLATZ. Die volle Liste steht immer in
   ihr im Fluss und bestimmt damit ihre Höhe — auch dann, wenn eine Position
   geöffnet ist und die Liste unsichtbar wird. Das Detail liegt absolut
   darüber und trägt zur Höhe nichts bei. Deshalb springt die Seite beim
   Öffnen und Schließen nicht, ohne dass irgendwo eine Pixelzahl stünde.

   ⛔ UND SIE NIMMT, WAS DIE KARTE ÜBRIG LÄSST (`flex: 1`, 06.09.2026). Die
   Karte ist so hoch wie die Zeichnung daneben (siehe `.wx-raster`); nach
   Kopfzeile und Einleitungssatz bleibt ein Rest, und der gehört der Bühne.
   Erst dadurch hat die Liste darin überhaupt etwas zu verteilen.
   ⚠️ `min-height: 0` ist Pflicht: Ein Flex-Kind wird von sich aus nie
   kleiner als sein Inhalt, und dann drückte die Liste die Karte wieder über
   die Zeichnung hinaus — die Zusage „bündig" hinge an einer Zufälligkeit. */
.wx-buehne {
  position: relative;
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

/* ⛔ DIE LISTE FÜLLT DIE BÜHNE, UND IHRE ZEILEN TEILEN SICH DEN PLATZ. Das
   ist die Stelle, an der aus „die Karte ist so hoch wie die Zeichnung" eine
   gleichmäßige Liste wird. */
.wx-liste {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  flex: 1;
  gap: var(--wx-luecke);
}

/* ⚠️ `visibility: hidden` statt `display: none` — es BEHÄLT DEN PLATZ, und
   genau der ist hier die Aufgabe. Es nimmt der verborgenen Liste zugleich
   die Fokussierbarkeit, was hier richtig ist: Der Weg zurück steht sichtbar
   im Detail darüber.
   ⛔ NUR IN DER ERSETZENDEN ANORDNUNG (07.09.2026). Inline bleibt die Liste
   stehen — dort ist die Beschreibung ein Zusatz unter einer Position und
   kein Ersatz für die Liste. */
.wx-buehne[data-wx-offen]:not([data-wx-anordnung="inline"]) .wx-liste {
  visibility: hidden;
}

/* ---------- Inline: die Beschreibung steht UNTER ihrer Position ----------

   ⛔ ZWEI ANORDNUNGEN, EINE STÜCKLISTE. Der Unterschied hängt allein an
   diesem Attribut; Markup und Verhalten sind dieselben, das Detail wandert
   beim Öffnen nur in die offene Position statt sich darüberzulegen.

   ⛔ HIER WIRD NICHT MEHR VERTEILT, UND DAS IST DER KERN. Die Grundregel
   streckt die Zeilen auf die Kartenhöhe (`flex: 1`); sobald eine
   Beschreibung dazukäme und die Liste überliefe, gäbe es nichts mehr zu
   verteilen — alle vierzehn Zeilen fielen im selben Augenblick von ihrer
   gestreckten Höhe auf die 40px-Untergrenze zurück. Ein Sprung durch die
   ganze Liste beim ersten Klick. Die Zeilen stehen deshalb auf ihrer
   natürlichen Höhe und rühren sich beim Öffnen nicht.
   ⚠️ Der Preis ist Fläche unten in der Karte, wo die Liste kurz ist
   (Detail B mit 9 Positionen, C mit 11). Die 40px-Trefferfläche bleibt
   unverändert die Untergrenze. */
.wx-buehne[data-wx-anordnung="inline"] {
  /* ⛔ HIER ROLLT ES, UND NUR HIER. Die Karte ist ab 1280px so hoch wie die
     Zeichnung und kann nicht wachsen (siehe Arena) — was nicht hineinpasst,
     rollt innerhalb der Bühne. Untereinander gibt es keinen Überlauf, die
     Deklaration bleibt dort folgenlos. */
  overflow-y: auto;
}
.wx-buehne[data-wx-anordnung="inline"] .wx-liste { flex: none; }
/* `block` statt `flex`: Das <li> trägt jetzt zwei Kinder untereinander —
   den Knopf und die geöffnete Beschreibung. Als Flex-Zeile stünden sie
   nebeneinander. */
.wx-buehne[data-wx-anordnung="inline"] .wx-pos { display: block; flex: none; }

/* ⛔ `height: auto` IST HIER PFLICHT, und das Gegenteil war ein stiller
   Fehler. Die Grundregel gibt dem Knopf `height: 100%`, damit er seine
   gestreckte Zeile ganz ausfüllt. Steht die Beschreibung im selben <li>,
   wird daraus eine Zirkularität: Das <li> ist so hoch wie Knopf UND
   Beschreibung, der Knopf nimmt sich diese ganze Höhe, und die Beschreibung
   rutscht unten heraus. Gemessen: <li> 306px hoch, Knopf ebenfalls 306px,
   die Beschreibung begann 2px UNTER dem Ende ihres eigenen <li> und ragte
   256px in die Zeilen darunter. Zu sehen war eine Liste, die sich selbst
   überlagert — und keine Meldung. Gestreckt wird hier ohnehin nicht, die
   40px-Trefferfläche trägt `min-height`. */
.wx-buehne[data-wx-anordnung="inline"] .wx-knopf { height: auto; }

/* ⛔ IM FLUSS, NICHT DARÜBER. `inset: auto` nimmt die Werte der Grundregel
   zurück — ohne das bliebe das Detail absolut und läge trotz `static`
   an seiner alten Stelle.
   Der Einzug links bindet es sichtbar an die Position darüber; die Kante
   sagt dasselbe noch einmal für den, der die Einrückung nicht liest. */
.wx-buehne[data-wx-anordnung="inline"] .wx-detail {
  position: static;
  inset: auto;
  margin: var(--abstand-xxs) 0 var(--abstand-s) var(--wx-nummer);
  padding: var(--abstand-s) var(--wx-luft);
  border-left: 2px solid var(--outline-variant);
  border-radius: 0;
  background: transparent;
}

/* ⛔ `flex: 1 0 auto` — WACHSEN JA, SCHRUMPFEN NEIN. Der Zuwachs wird
   gleichmäßig verteilt, die natürliche Höhe ist die Untergrenze. Mit
   `1 1 0` wären alle Zeilen exakt gleich hoch, aber eine zweizeilig
   umbrechende Bezeichnung liefe aus ihrer Zeile heraus — und zwar lautlos.
   So bleibt die Liste in jeder Lage vollständig lesbar; dass die Zeilen
   dann um den Betrag eines Umbruchs auseinandergehen, sieht man eher als
   abgeschnittenen Text. */
.wx-pos { display: flex; flex: 1 0 auto; }

/* Die ganze Zeile ist das Ziel, nicht nur die Beschriftung. */
.wx-knopf {
  display: flex;
  align-items: center;
  gap: var(--wx-luft);
  width: 100%;
  /* ⛔ Der Knopf füllt seine Zeile ganz — sonst läge die Trefferfläche als
     40px-Streifen in einer 67px hohen Zeile, und der Hover zeichnete einen
     Balken, der zur Zeile daneben nicht passt.
     ⚠️ `--wx-zeile` ist die UNTERGRENZE und keine Vorgabe mehr: Sie ist
     zugleich eine bequeme Trefferfläche und darf nicht darunter fallen. */
  height: 100%;
  min-height: var(--wx-zeile);
  padding: var(--abstand-xs) var(--wx-luft);
  border: 0;
  border-radius: var(--radius-m);
  background: transparent;
  color: var(--on-surface);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background-color var(--dauer-kurz) var(--kurve);
}
.wx-knopf:hover { background: var(--zustand-hover); }
.wx-knopf:active { background: var(--zustand-aktiv); }
.wx-pos-name { font-size: var(--wx-name); }

/* ⛔ Die Nummer wächst MIT, sonst schwimmt ein 28px-Kreis in einer 84px
   hohen Zeile. Der Selektor ist zweiteilig, weil .wn-nummer--klein aus der
   Bauteilliste darüber Breite, Höhe und Schriftgröße fest setzt und sonst
   gewönne. ⚠️ Die Rundung bleibt --radius-pille, also ein Kreis. */
.wx-knopf .wn-nummer {
  width: var(--wx-nummer);
  height: var(--wx-nummer);
  font-size: var(--wx-nummer-schrift);
}

/* ⛔ OHNE DIESE ZEILE LÄGE DAS DETAIL DAUERHAFT ÜBER DER LISTE. Das Projekt
   führt keine globale [hidden]-Regel, und das `display: flex` darunter
   schlüge die Vorgabe des Browsers. Dieselbe Falle wie bei
   .wb-platzhalter, .such-treffer, .zaehlmarke und .feld-rest. */
.wx-detail[hidden] { display: none; }

.wx-detail {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--abstand-s);
  border-radius: var(--radius-m);
  background: var(--surface-container);
}
.wx-detail-kopf {
  display: flex;
  align-items: center;
  gap: var(--wx-luft);
}
.wx-detail-name {
  margin: 0;
  font-family: var(--schrift-serif);
  font-size: var(--wx-detail-name);
  font-weight: 600;
  color: var(--on-surface);
}
/* ⛔ Die Kurzangabe steht ÜBER dem Langtext und hebt sich von ihm ab: Sie
   ist dieselbe Zeile, die auch die Liste zeigt, und bleibt deshalb die
   Antwort auf „was ist das" — der Absatz darunter führt sie aus. Gleich
   gesetzt verlöre sie diesen Rang und wäre nur der erste Satz eines
   Fließtextes. */
.wx-detail-satz {
  margin: 0;
  max-width: var(--lesebreite);
  font-size: var(--wx-detail-kurz);
  font-weight: 500;
  color: var(--on-surface);
}
.wx-detail-lang {
  margin: 0;
  max-width: var(--lesebreite);
  font-size: var(--wx-detail-lang);
  line-height: var(--zeile-normal);
  color: var(--on-surface-variant);
}
/* Ohne Langtext entfällt der Absatz ganz — ein leeres <p> behielte seinen
   Zeilenabstand und risse eine Lücke, die wie ein Ladefehler aussieht. */
.wx-detail-lang[hidden] { display: none; }
/* ⛔ DER WEG ZURÜCK STEHT UNTER DEM TEXT, NICHT AM KARTENENDE (06.09.2026).
   Bis dahin schob ihn `margin-top: auto` an den Fuß der Bühne — gemessen bei
   1440px lagen zwischen dem Ende der Beschreibung und dem Knopf bis zu
   954px Leere, und er las sich als Fußleiste einer Karte statt als Weg
   zurück aus dem Text, den man gerade liest. Jetzt trägt ihn der `gap` der
   Bühne wie jeden anderen Absatz auch.
   ⚠️ Der Nebeneffekt ist erwünscht: Er rückt nach OBEN, kann also nicht
   mehr unten aus der Bühne laufen — die Zusage „der Knopf bleibt innerhalb"
   wird dadurch leichter zu halten, nicht schwerer. */
.wx-zurueck { flex: 0 0 auto; }

/* ---------- Resin: Schichtschnitt ---------- */

.wr-raster {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--abstand-xl);
  align-items: start;
}
.wr-karte {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-l);
  padding: var(--abstand-l);
  border-radius: var(--radius-l);
  background: var(--surface-container);
  box-shadow: var(--karten-kante);
}
.wr-stapel {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--abstand-xs);
}
.wr-schicht { display: flex; align-items: center; gap: var(--abstand-m); }
/* Die Höhe steht am Element (js: RESIN_SCHICHTEN), weil sie eine Aussage
   trägt: Die FEP-Folie ist der flachste Balken, weil sie das dünnste Bauteil
   des Stapels ist. */
.wr-balken {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  min-width: 0;
  padding: 0 var(--abstand-l);
  border-radius: var(--radius-s);
  color: var(--resin-schicht-tinte);
  font-size: var(--title-medium);
}
.wr-balken--1 { background: var(--resin-schicht-1); }
.wr-balken--2 { background: var(--resin-schicht-2); }
.wr-balken--3 { background: var(--resin-schicht-3); }
.wr-balken--4 { background: var(--resin-schicht-4); }
.wr-balken--5 { background: var(--resin-schicht-5); }
.wr-balken--6 { background: var(--resin-schicht-6); }
.wr-hinweis {
  display: flex;
  align-items: flex-start;
  gap: var(--abstand-m);
  margin: 0;
  padding: var(--abstand-l);
  border-radius: var(--radius-m);
  background: var(--surface-container-highest);
  font-size: var(--body-medium);
  color: var(--on-surface-variant);
}
.wr-symbol { flex: 0 0 auto; width: 20px; height: 20px; color: var(--primary); }
.wr-spalte { display: flex; flex-direction: column; gap: var(--abstand-xl); }
.wr-liste {
  list-style: none;
  margin: 0;
  padding: 0;
  border-radius: var(--radius-l);
  background: var(--surface-container);
  box-shadow: var(--karten-kante);
}
.wr-zeile {
  display: flex;
  align-items: flex-start;
  gap: var(--abstand-m);
  padding: var(--abstand-l);
}
.wr-zeile + .wr-zeile { border-top: 1px solid var(--outline-variant); }

/* ---------- Ablauf in sechs Schritten ---------- */

.wa-raster {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
  gap: var(--abstand-xl);
}
.wa-spalte { display: flex; flex-direction: column; gap: var(--abstand-s); }
.wa-liste {
  list-style: none;
  margin: 0;
  padding: 0;
  border-radius: var(--radius-l);
  background: var(--surface-container);
  box-shadow: var(--karten-kante);
}
.wa-schritt {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--abstand-xs) var(--abstand-m);
  padding: var(--abstand-m) var(--abstand-l);
}
.wa-schritt + .wa-schritt { border-top: 1px solid var(--outline-variant); }
/* Die Ziffer ist die Schrittnummer, nicht die Bauteilnummer — deshalb kleiner
   als .wn-nummer und ohne führende Null. Blau bei FDM, Rosé bei Resin: Die
   beiden Spalten laufen nebeneinander und sollen sich auf einen Blick
   auseinanderhalten lassen. */
.wa-ziffer {
  flex: 0 0 auto;
  align-self: center;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: var(--radius-pille);
  font-family: var(--schrift-mono);
  font-size: var(--label-small);
  font-weight: 500;
}
.wa-liste--fdm .wa-ziffer {
  background: var(--primary-container);
  color: var(--on-primary-container);
}
.wa-liste--resin .wa-ziffer {
  background: var(--tertiary-container);
  color: var(--on-tertiary-container);
}
.wa-titel { font-size: var(--title-medium); font-weight: 500; color: var(--on-surface); }
.wa-satz { font-size: var(--body-medium); color: var(--on-surface-variant); }

/* ---------- Was das Gerät können muss ---------- */

/* ⛔ DIE TRENNLINIEN SIND FUGEN, KEINE RÄNDER. Der Behälter trägt die
   Linienfarbe als Fläche, die Kacheln legen ihre eigene darüber, und die 1px
   Lücke dazwischen bleibt als Linie stehen. Über border-left/-top ginge es
   nicht sauber: Wie viele Spalten es gibt, entscheidet auto-fit erst zur
   Laufzeit — jede :first-child- oder nth-child-Regel wäre auf genau eine
   Spaltenzahl gerechnet, und bei jeder anderen liefe eine Linie an der
   Außenkante entlang. Genau so sah es beim Bauen aus. */
.wg-raster {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: 1px;
  border-radius: var(--radius-l);
  background: var(--outline-variant);
  box-shadow: var(--karten-kante);
  overflow: hidden;
}
.wg-karte {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-s);
  padding: var(--abstand-l);
  background: var(--surface-container);
}
.wg-titel {
  margin: 0;
  font-size: var(--title-medium);
  font-weight: 500;
  color: var(--on-surface);
}
.wg-satz { margin: 0; font-size: var(--body-medium); color: var(--on-surface-variant); }
/* Der Weg zum Filter. Er führt auf die LISTE, nicht auf ein gesetztes
   Kriterium — das sagt der Text, den js/seite-wissen.js mitgibt. */
.wg-verweis {
  margin-top: auto;
  padding-top: var(--abstand-s);
  font-family: var(--schrift-mono);
  font-size: var(--label-medium);
  color: var(--primary);
}

/* ---------- Begriffe ---------- */

/* Fugen statt Ränder, wie eine Fläche höher — Begründung dort.
   ⚠️ 320px als Mindestbreite und nicht 260: Die Vorlage stellt acht Kacheln
   in vier Spalten, und bei 260px passten in die 1435px breite Inhaltsspalte
   fünf. Dann stünde in der zweiten Reihe eine einzelne Kachel neben drei
   Lücken. Mit 320px bleibt es bis 1600px Inhaltsbreite bei vier. */
.wk-raster {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: 1px;
  border-radius: var(--radius-l);
  background: var(--outline-variant);
  box-shadow: var(--karten-kante);
  overflow: hidden;
}
.wk-kachel {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-xs);
  padding: var(--abstand-l);
  background: var(--surface-container);
}
.wk-name {
  margin: 0;
  font-size: var(--title-medium);
  font-weight: 500;
  color: var(--on-surface);
}
.wk-tag {
  font-family: var(--schrift-mono);
  font-size: var(--label-small);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--on-surface-variant);
  white-space: nowrap;
}
.wk-satz { margin: 0; font-size: var(--body-medium); color: var(--on-surface-variant); }

/* ---------- Weiter ---------- */

.ww-raster {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: var(--abstand-xl);
}
.ww-karte {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-xs);
  padding: var(--abstand-l);
  border-radius: var(--radius-l);
  background-color: var(--surface-container);
  box-shadow: var(--karten-kante);
  color: inherit;
  text-decoration: none;
  background-image: linear-gradient(transparent, transparent);
  transition: background-image var(--dauer-kurz) ease;
}
.ww-karte:hover {
  background-image: linear-gradient(var(--zustand-hover), var(--zustand-hover));
}
.ww-karte:active {
  background-image: linear-gradient(var(--zustand-aktiv), var(--zustand-aktiv));
}
.ww-titel {
  margin: var(--abstand-xs) 0 0;
  font-size: var(--title-large);
  font-weight: 500;
  color: var(--on-surface);
}
.ww-satz { margin: 0; font-size: var(--body-medium); color: var(--on-surface-variant); }

/* ---------- Schmale Fenster ----------

   Alles, was nebeneinander steht, geht untereinander. Die Zeichnung bleibt
   quadratisch und behält damit ihre Marker an Ort und Stelle. */
@media (max-width: 899px) {
  .wr-raster, .wx-raster, .wg-liste {
    grid-template-columns: minmax(0, 1fr);
  }
  /* Untereinander steht die Zeichnung oben und die Stückliste darunter —
     das ist die Reihenfolge im Markup, es ist nichts umzustellen.
     ⛔ Die Bühne behält ihr Verhältnis und damit ihre Skalierung: Sie
     rechnet gegen die BREITE ihres Containers (cqw), und die ist hier die
     volle Spalte statt der halben. Eine Höhenbegrenzung wäre der Fehler —
     sie schnitte die Zeichnung ab, statt sie kleiner zu machen. */
  .wissen-seite { gap: var(--abstand-3xl); }
}

/* ==================================================================
   Kontaktformular (/kontakt)

   Die Seite liegt in `.textseite` und benutzt deren Karten — hier steht nur,
   was das Formular darin braucht. Die Breite kommt von `--lesebreite`, nicht
   von der Karte: Die Textseiten stehen auf 1120px, und ein Eingabefeld über
   die volle Kartenbreite wäre auf dem Desktop rund einen Meter lang.
   ================================================================== */

.kf-formular {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-l);
  max-width: var(--lesebreite);
  margin-top: var(--abstand-l);
}

.kf-feld {
  display: flex;
  flex-direction: column;
  gap: var(--abstand-xs);
  /* ⚠️ Ein Eingabefeld bringt seine eigene min-content-Breite mit (Chrome
     rechnet mit rund 20 Zeichen). Ohne diese Zeile kann die Spalte darum
     herum nicht schmaler werden als das Feld, und bei 320px läuft die Karte
     über — verdeckt von `overflow-x: clip` am <html>, also lautlos. Dieselbe
     Falle wie bei `.ordner-karte`, `.sortier-option` und dem Suchfeld der
     Startseite. */
  min-width: 0;
}

.kf-beschriftung {
  font-size: var(--body-medium);
  color: var(--on-surface-variant);
}

/* ⛔ Die Kante nimmt `--outline`, nicht `--feld-kante`.

   `--feld-kante` ist die Haarlinie in `--outline-variant` und im Dunkelmodus
   auf `none` — sie ist eine Verstärkung der Flächenstufe, keine Grenze. Hier
   IST die Kante die Grenze des Bedienelements, und dafür fordert WCAG 1.4.11
   ihre 3:1. Dieselbe Rolle und dieselbe Begründung wie beim Suchfeld der
   Listen und beim ungewählten Filter-Chip. */
.kf-eingabe {
  width: 100%;
  max-width: 100%;
  padding: var(--abstand-s) var(--abstand-m);
  border: 0;
  border-radius: var(--radius-s);
  background: var(--surface-container-highest);
  color: var(--on-surface);
  box-shadow: inset 0 0 0 1px var(--outline);
  font-size: var(--body-medium);
}

/* Senkrecht ja, waagerecht nein: Ein in die Breite gezogenes Textfeld sprengt
   die Karte, und der Zugewinn wäre eine längere Zeile in einem Formular, das
   ohnehin auf Lesebreite steht. */
textarea.kf-eingabe {
  resize: vertical;
  min-height: 160px;
  line-height: 1.5;
}

/* ---------- Der Honigtopf ----------

   ⛔ WEDER `display: none` NOCH `visibility: hidden`, und das ist der ganze
   Sinn: Beide erkennt ein Formularroboter an einer einzigen Abfrage und
   überspringt das Feld. Versteckt wird deshalb mit denselben Mitteln wie
   `.sr-only` — aus dem Fluss geschoben und auf einen Punkt beschnitten.

   Aus der Tabreihenfolge hält es `tabindex="-1"`, aus der Vorlesereihenfolge
   `aria-hidden` am Behälter (beides im Markup, js/seite-kontakt.js). */
.kf-honigtopf {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  clip: rect(0 0 0 0);
  overflow: hidden;
  white-space: nowrap;
}

.kf-zustimmung {
  display: flex;
  align-items: flex-start;
  gap: var(--abstand-s);
  font-size: var(--body-medium);
  color: var(--on-surface);
}

/* Das Kästchen behält seine Größe, auch wenn der Satz daneben dreizeilig
   umbricht — `flex: 0 0 auto` ist dafür Pflicht. Der Versatz nach unten setzt
   es auf die erste Textzeile statt auf die Oberkante des Absatzes. */
.kf-zustimmung input[type="checkbox"] {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  margin-top: 2px;
  accent-color: var(--primary);
  cursor: pointer;
}

.kf-zustimmung label { cursor: pointer; }

.kf-fuss { display: flex; }

/* ⛔ DIESE ZEILE IST PFLICHT.

   Das Projekt führt keine globale `[hidden]`-Regel, und das `display` einer
   Klasse schlägt die Vorgabe des Browsers — die Meldung läge sonst dauerhaft
   sichtbar da, mit leerem Text. Dieselbe Falle wie bei `.wb-platzhalter`,
   `.wx-detail`, `.such-treffer`, `.zaehlmarke` und `.feld-rest`. */
.kf-meldung[hidden] { display: none; }

.kf-meldung {
  display: block;
  margin: 0;
  padding: var(--abstand-s) var(--abstand-m);
  border-radius: var(--radius-s);
  background: var(--surface-container-highest);
  font-size: var(--body-medium);
  color: var(--on-surface);
}

.kf-meldung--erfolg { color: var(--gruen-inhalt); }
.kf-meldung--fehler { color: var(--error); }

.kf-direkt {
  margin: var(--abstand-l) 0 0;
  max-width: var(--lesebreite);
  font-size: var(--body-medium);
  color: var(--on-surface-variant);
}


/* ==========================================================================
   DIE MOBILE FASSUNG DER PRODUKTLISTEN (18.09.2026, zwei Bildvorlagen)
   ==========================================================================

   Gilt für /filamente UND /resine, in Karten- wie in Tabellenansicht. Die
   DESKTOP-FASSUNG IST UNBERÜHRT: Alles hier steht in einer Medienabfrage, und
   was textlich abweicht, steht als zweite Fassung im Markup daneben (die
   Spaltenüberschriften, die Unterzeile der Zeile, das Wort „gesamt").

   ⛔ DIE GRENZE IST 767px, und sie ist nicht frei gewählt: Dort wird die
   Kopfleiste zweizeilig (css/layout.css), dort entfällt der Kategoriereiter
   „Wissen", und dort beginnt die Fassung, die die beiden Vorlagen zeigen.
   Eine eigene Zahl für die Liste ließe Kopf und Inhalt auf verschiedenen
   Breiten umschalten — sichtbar als Seite, die sich in zwei Schritten
   verwandelt.

   ⛔ DIE WERKZEUGZEILE IST AUF DIE PRODUKTLISTEN EINGEGRENZT
   (`:has([data-rolle="versand"])`). Dieselbe `.listen-kopf` trägt /drucker,
   und dort gibt es keinen Versandschalter, dafür die Bauartwahl und einen
   anderen Sortierknopf. Ein Raster mit benannten Feldern träfe dort auf
   andere Kinder, und die landeten stillschweigend in der ersten freien Zelle.
   ========================================================================== */
@media (max-width: 767px) {

  /* ---------- Werkzeugzeile: dieselbe Abfolge, nur umbrochen ---------------

     ⛔ DIE ABFOLGE IST AUF ALLEN DREI LISTEN UND AUF JEDER BREITE DIESELBE:
     Filter · Sortierung · Versand, dann Ansichtswahl · Trefferzahl. Sie steht
     im Markup; das Raster hier legt sie nur in Reihen und dreht sie nirgends
     um. Ein Raster, das die Reihenfolge ändert, wäre auf dem Handy eine andere
     Zeile als am Desktop — und für Tastatur und Vorlesehilfe weiterhin die
     alte, denn die folgen dem Markup.

         ab 560px   Filter · Sortierung · Versand  /  Ansicht · Treffer
         ab 350px   Filter · Sortierung  /  Versand  /  Ansicht · Treffer
         darunter   Filter / Sortierung / Versand  /  Ansicht · Treffer

     ⚠️ DIE LINKE GRUPPE STEHT OBEN, nicht die rechte. Beim Umbruch bleibt
     damit dieselbe Folge wie in einer Zeile: Erst wird eingestellt, dann
     beschrieben. Eine Stufe, die Ansicht und Treffer nach oben zöge, wäre
     genau die Umstellung, die dieser Block vermeidet.

     ⛔ DIE GRENZE IST HIER 639px, NICHT 767px — anders als bei Kachel und
     Tabelle eine Zeile weiter unten. Sie ist nicht frei gewählt: Genau ab
     dort schrumpfen die Knöpfe beim Rollen (--werkzeug-roll, js/app.js
     WERKZEUG_BREITE), und genau deshalb braucht es dort ein Raster mit fester
     Reihenzahl. Oberhalb schrumpft nichts, also kann die Zeile gefahrlos
     umbrechen — und tut es erst, wenn der Platz wirklich nicht reicht. Bei
     640px ergibt der Umbruch dasselbe Bild wie das Raster bei 639px; die
     Grenze ist deshalb nicht zu sehen.

     ⛔ `.filter-zeile` wird zu `display: contents`, damit ihre drei Knöpfe zu
     Kindern desselben Rasters werden. Anders ließe sich der Versandschalter
     nicht aus der Knopfzeile in eine eigene Reihe holen: CSS verschiebt ein
     Element innerhalb seines Elternteils, nie darüber hinaus. Das Markup
     bleibt dadurch für beide Fassungen dasselbe.

     ⛔ EIN RASTER MIT BENANNTEN FELDERN, KEIN flex-wrap. Die Zahl der Reihen
     hängt damit allein an der Breite, nie an der Größe der Knöpfe — und die
     schrumpft beim Rollen. Mit flex-wrap kippte der Umbruch mitten im
     Rollweg, und die Zeilenhöhe spränge um eine ganze Knopfreihe: genau der
     Fehler, der am 02.09.2026 schon einmal behoben wurde.

     ⚠️ Die erste Spalte nimmt das Maß der Ansichtswahl — sie ist dort das
     breiteste Stück und schrumpft NICHT mit dem Rollfortschritt (die
     Verkleinerung trifft nur `.filter-zeile .knopf`). Damit ist die
     Spaltenbreite rollstabil, und mit ihr die Reihenzahl.

     ⛔ JEDES FELD IST IN JEDER STUFE BENANNT — keines wird automatisch
     platziert. Ein `grid-area: auto / 1 / auto / -1` hängte die Zeile hinter
     die benannten Reihen, und dort ist seit dem Umdrehen der Abfolge die
     rechte Gruppe: Der Versandschalter rutschte still UNTER Ansichtswahl und
     Trefferzahl, während das Markup ihn davor führt. */
  @media (max-width: 639px) {
    .listen-kopf:has([data-rolle="versand"]) {
      display: grid;
      grid-template-columns: max-content max-content minmax(0, 1fr);
      grid-template-areas:
        "filter  sortieren versand"
        "ansicht treffer   .";
      align-items: center;
      justify-items: start;
      row-gap: var(--abstand-s);
    }
    .listen-kopf:has([data-rolle="versand"]) .filter-zeile { display: contents; }
    /* ⚠️ Die Hülle der rechten Gruppe löst sich auf, damit ihre beiden
       Kinder Felder DESSELBEN Rasters werden — dieselbe Technik und derselbe
       Grund wie bei der Knopfzeile eine Zeile darüber. Mit ihr fällt ihr
       Auto-Rand weg: Im Raster entscheidet das Feld über die Lage. */
    .listen-kopf:has([data-rolle="versand"]) .listen-kopf-rechts { display: contents; }
    .listen-kopf:has([data-rolle="versand"]) .ansichtswahl            { grid-area: ansicht; }
    .listen-kopf:has([data-rolle="versand"]) .listen-treffer          { grid-area: treffer; }
    .listen-kopf:has([data-rolle="versand"]) [data-rolle="filter"]    { grid-area: filter; }
    .listen-kopf:has([data-rolle="versand"]) [data-rolle="sortieren"] { grid-area: sortieren; }
    .listen-kopf:has([data-rolle="versand"]) [data-rolle="versand"]   { grid-area: versand; }

    /* ⛔ DIE SCHWELLE IST GEMESSEN, NICHT GERECHNET — und die Rechnung hätte
       sie um 40px verfehlt. Maßgeblich ist nicht, ob die drei Knöpfe
       nebeneinander passen, sondern ob „Versand einrechnen" in der dritten
       Spalte noch EINZEILIG steht: Sobald seine Beschriftung umbricht, wird
       die zweite Rasterreihe höher — und zwar nur in Ruhe, denn beim Rollen
       schrumpft der Knopf und die Beschriftung paßt wieder. Gemessen sprang
       die Zeile dadurch bei 530px von 110,8 auf 100,0px, sobald man zu rollen
       begann. Das Raster hält zwar die Reihen ZAHL fest, nicht aber ihre
       HÖHE; der Umbruch innerhalb eines Knopfes ist dieselbe Falle wie der
       Umbruch zwischen ihnen.

       Sauber ab 540px (/filamente) bzw. 530px (/resine) — die Trefferzahl ist
       dort verschieden breit und bestimmt die zweite Spalte mit. Gesetzt sind
       560px: 20px Reserve für eine längere Trefferzahl, und es ist die
       Breite, auf der beide Listen gemessen einwandfrei stehen. */
    @media (max-width: 559px) {
      .listen-kopf:has([data-rolle="versand"]) {
        grid-template-columns: max-content minmax(0, 1fr);
        grid-template-areas:
          "filter  sortieren"
          "versand versand"
          "ansicht treffer";
      }
      /* Die Ansichtswahl ist mit 186,3px das breiteste Einzelstück der Zeile
         und bestimmt damit die erste Spalte. Engeres Polster bringt sie auf
         rund 170px und hält so die Trefferzahl daneben. Die Beschriftungen
         bleiben: „Karten" und „Tabelle" SIND die Beschriftung der beiden
         Schalter. */
      .listen-kopf:has([data-rolle="versand"]) .ansichtchip {
        padding-inline: var(--abstand-s);
      }
    }

    /* Gerechnet: Spalte 1 misst Fensterbreite − 15 (Rollbalken) − 32
       (Seitenrand) − 8 (Lücke) − 170,3 (Ansichtswahl — sie ist auch hier das
       breiteste Stück der Spalte). Für die 124,2px der Sortierung reicht das
       bis 349,5px hinunter; darunter bekommt jeder Knopf seine eigene Reihe.
       Die LETZTE Reihe bleibt auch dort, wie sie ist — Ansichtswahl und
       Trefferzahl brauchen zusammen 252,3px. */
    @media (max-width: 349px) {
      .listen-kopf:has([data-rolle="versand"]) {
        grid-template-areas:
          "filter    filter"
          "sortieren sortieren"
          "versand   versand"
          "ansicht   treffer";
      }
    }
  }


  /* ---------- Kachel (Bildvorlage 1) ---------- */

  /* Die Versandzeile trägt hier alle drei Fassungen und keine Pillenform
     mehr: „Versandfrei" grün, Aufschlag und fehlende Angabe gedämpft neutral.
     ⛔ NUR „frei" IST GRÜN — grün heißt auf dieser Seite „gut", und ein
     Aufpreis ist es nicht. Die Bildvorlage setzt beides grün. */
  .versandzeile {
    display: block;
    /* `auto` statt `max-content`: Ohne Pille ist sie eine Textzeile, und
       „ohne Versandangabe" braucht rund 130px — in der Titelspalte einer
       320px-Kachel bleiben davon 72px. Sie muss also umbrechen dürfen. */
    width: auto;
    padding: 0;
    background: none;
    font-size: var(--body-small);
  }
  .versandzeile--auf,
  .versandzeile--unbekannt {
    display: block;
    color: var(--on-surface-variant);
  }

  /* Unterscheidet den Rollenpreis vom Vergleichspreis darüber — beide sind
     Beträge, und ohne das Wort stünden zweimal Euro untereinander. */
  .kachel-gesamt-wort { display: inline; }

  /* ⛔ DER SHOP-KNOPF IST GEFÜLLT (Bildvorlage): Er ist auf dem Handy die
     einzige Handlung, die aus der Kachel hinausführt, und steht über die
     volle Breite unter dem Preis. Am Desktop bleibt er umrandet — dort trägt
     die Kachel weniger Höhe, und in einem Raster aus vier Kacheln zögen vier
     gefüllte Knöpfe den Blick von den Preisen ab. */
  .kachel-shop .knopf--umrandet {
    background: var(--primary);
    color: var(--on-primary);
    border-color: transparent;
  }

  /* ---------- Tabelle (Bildvorlage 2) ----------

     DREI Spalten: Produkt (mit Unterzeile), Vergleichspreis, Gesamtpreis.

     ⛔ DIE CONTAINER-LEITER DER DESKTOP-TABELLE GILT HIER NICHT MEHR. Sie
     gibt von rechts nach links Spalten ab und würde als vorletzte Stufe
     ausgerechnet `.sp-angebot` ausblenden — also die Spalte, die in dieser
     Fassung „Gesamt" heißt und bleiben muß. Die mobile Tabelle ist keine
     schmale Stufe derselben Leiter, sondern eine eigene Anordnung: Farbe,
     Shop und Menge stehen hier in der Unterzeile beim Namen statt in eigenen
     Spalten. Deshalb wird jede Spalte hier ausdrücklich gesetzt.

     ⚠️ `.produkt-tabelle` grenzt das auf die Produktliste ein. Die
     Angebotstabelle der Detailseite trägt dieselbe Grundgestaltung
     `.angebots-tabelle`, hat aber andere Spalten. */
  .produkt-tabelle .sp-material,
  .produkt-tabelle .sp-farbe,
  .produkt-tabelle .sp-shop,
  .produkt-tabelle .sp-aktionen { display: none; }
  .produkt-tabelle .sp-angebot { display: table-cell; }

  /* Kein Vorschaubild: Die Vorlage zeigt keines, und 40px Bild plus Lücke
     kosten ein Fünftel der Produktspalte — auf Kosten des Namens, um
     dessentwillen man die Zeile liest. */
  .produkt-tabelle .tz-bild { display: none; }

  /* Beträge rechtsbündig: Zwei Zahlenspalten nebeneinander liest man an ihrer
     rechten Kante entlang, nicht an der linken. */
  .produkt-tabelle .sp-vergleich,
  .produkt-tabelle .sp-angebot {
    text-align: right;
    white-space: nowrap;
  }

  /* Die kurze Spaltenüberschrift; die ausgeschriebene bleibt dem Desktop.
     Die Versalien macht `text-transform` an `.angebots-tabelle thead th` —
     im Markup steht gemischter Text (Vorlesehilfen buchstabieren
     durchgehende Versalien sonst teilweise). */
  .produkt-tabelle .th-lang { display: none; }
  .produkt-tabelle .th-kurz { display: inline; }

  /* Farbe · Shop · Menge unter dem Namen statt in drei eigenen Spalten. */
  .produkt-tabelle .tz-unterzeile { display: block; }
  .produkt-tabelle .tz-menge { display: none; }
  .produkt-tabelle .sp-angebot .versandzeile { display: block; }

  /* Der Name ist kein Flex-Element mehr, sondern ein Block mit zwei Zeilen. */
  .produkt-tabelle .tz-name {
    display: block;
    font-weight: 600;
  }
  .produkt-tabelle .tz-titel { display: block; }

  /* ⛔ DIE PRODUKTSPALTE MUSS HIER SCHRUMPFEN DÜRFEN (gemessen 18.09.2026).

     Beide Untergrenzen der Desktop-Fassung gelten für eine Tabelle mit
     sieben Spalten und einem 40px-Vorschaubild; in der mobilen Fassung gibt
     es drei Spalten und kein Bild. Stehen sie trotzdem, rollt die Tabelle:
     bei 360px Fenster um 15px, bei 320px um 55.

     ⚠️ `overflow-wrap: anywhere` und nicht `break-word` — nur das erste geht
     in die MINDESTBREITE ein. Eine Tabellenzelle bemisst sich am längsten
     unteilbaren Wort, und `min-width: 0` allein ändert daran nichts; mit
     `break-word` blieb „Prusament" als Block stehen und die Tabelle rollte
     weiter. Dieselbe Unterscheidung wie bei `.ok-name` im Merkzettel. */
  .produkt-tabelle { min-width: 0; }
  .produkt-tabelle .sp-produkt { min-width: 0; }

  /* Enger gesetzt als am Desktop — drei Spalten teilen sich bei 320px
     Fenster 273px, und 6px Polster je Zellenseite sind davon 36px. */
  .produkt-tabelle th,
  .produkt-tabelle td { padding-inline: 4px; }

  /* ⚠️ EINE SCHRIFTSTUFE KLEINER, und aus demselben Grund wie beim Preis der
     Kachel (css/komponenten.css, „AUF DEN SCHMALSTEN GERÄTEN"): Er steht auf
     `nowrap` — eine umgebrochene Zahl wäre keine Preisangabe mehr —, und
     „ab 24,90 €/l" braucht bei 18px 126px in einer Spalte, der bei 320px
     rund 100px bleiben. Er bleibt die auffälligste Zahl der Zeile: Serif,
     fett, in --primary. */
  .produkt-tabelle .tz-vergleichspreis { font-size: var(--title-small); }
  .produkt-tabelle .tz-titel,
  .produkt-tabelle .tz-unterzeile { overflow-wrap: anywhere; }

  /* ⛔ DIE GANZE ZEILE IST ANTIPPBAR (Vorlage). Dieselbe Technik wie bei
     `.ok-hauptlink` und `.kachel-hauptbereich`: ein ::after am einzigen Link,
     das die Zeile abdeckt. Es bleibt bei EINEM Tabstopp je Zeile.
     ⚠️ Möglich ist das erst, seit die Aktionsspalte hier ausgeblendet ist —
     sonst deckte die Fläche Herz und Vergleichsknopf mit ab. */
  .produkt-tabelle .tabellen-zeile { position: relative; }
  .produkt-tabelle .tz-name::after {
    content: "";
    position: absolute;
    inset: 0;
  }

  /* ⛔ KEINE KARTENFLÄCHE, NUR TRENNLINIEN (Vorlage). Am Desktop steht die
     Tabelle als Karte in der Liste; auf dem Handy nimmt sie die volle Breite,
     und eine Karte um eine randlose Liste wäre ein Rahmen um nichts.
     ⚠️ `padding: 0` gehört dazu: Das 1px-Polster hält die Kante frei, die es
     hier nicht mehr gibt. */
  .liste-spalte .tabelle-rahmen {
    background: none;
    box-shadow: none;
    border-radius: 0;
    padding: 0;
  }
  .produkt-tabelle thead th { background: var(--background); }
}
