/* =========================================================================
   KMU Software — Stylesheet
   Die Vergleichstabelle ist die Startansicht. Alles andere ist Beiwerk und
   sitzt unterhalb. Gestaltung: Schweizer Typografie, Haarlinien, ein Akzent.
   ========================================================================= */

:root {
  --bg:        #f4f4f1;
  --surface:   #ffffff;
  --surface-2: #fafaf8;
  --ink:       #14161a;
  --ink-soft:  #3d4249;
  --muted:     #71767e;
  --line:      #e3e3dd;
  --line-hard: #cfcfc8;
  --accent:    #d8232a;
  --accent-ink:#ffffff;
  --good:      #1a7a4c;
  /* Die fünf Töne der Musterbetriebe. Eine REIHENFOLGE von hell nach dunkel
     mit der Betriebsgrösse, keine Palette — damit man ohne Nachschlagen sieht,
     ob der Preis mit dem Betrieb wächst.

     Bunt war ein Versuch (Okabe/Ito, für Farbfehlsichtige unterscheidbar) und
     ist wieder heraus: fünf Farben unterschieden zwar besser, aber sie geben
     der Reihe keine Richtung mehr, und Rot ist auf dieser Seite schon dreifach
     belegt. Deckungsgleiche Punkte weichen jetzt senkrecht aus — das war das
     eigentliche Problem, nicht die Farbe.

     Sie stehen hier und nicht in der Grafik, weil beide Fassungen sie brauchen:
     das Punktdiagramm im CSS und ECharts, das sie zur Laufzeit ausliest. Eine
     Quelle, kein Abgleich. */
  --ton-0: #c9c6bd;
  --ton-1: #a6a299;
  --ton-2: #7d7972;
  --ton-3: #52504b;
  --ton-4: #1c1b19;
  --shadow:    0 1px 2px rgba(20,22,26,.04), 0 8px 24px -12px rgba(20,22,26,.14);
  --shadow-lg: 0 24px 60px -20px rgba(20,22,26,.30);
  --r:  10px;
  --r-s: 6px;
  --maxw: 1560px;
  --head-h: 46px;
  /* Kopf- und Fussleiste sind IMMER dunkel, auch im Hellmodus. Eigene
     Variablen, damit die Elemente darin nicht auf die Seitenpalette zeigen —
     sonst hätte jede spätere Farbänderung dort unlesbaren Text erzeugt. */
  --bar-bg:    #14161a;
  --bar-ink:   #f2f3f5;
  --bar-muted: #9aa1a9;
  --bar-line:  #2c3138;
  /* Eigenes Rot für die Schweiz-Leiste: tiefer als --accent, damit weisser Text
     darauf 5.9:1 erreicht statt 4.6:1. Bei 13px Schrift ist das der Unterschied
     zwischen lesbar und knapp. */
  --ch-red:    #c8102e;
  color-scheme: light;
}

html[data-theme="dark"] {
  --bg:        #0e1013;
  --surface:   #16191d;
  --surface-2: #1b1f24;
  --ink:       #eceef0;
  --ink-soft:  #c3c8ce;
  --muted:     #8d949c;
  --line:      #262a30;
  --line-hard: #343a41;
  --accent:    #ff4b52;
  --accent-ink:#1a0002;
  --good:      #3ec287;
  /* Im Dunkelmodus kehrt sich die Reihe um: dort ist HELL das Kräftige. Die
     Bedeutung bleibt — der grösste Betrieb sticht hervor. */
  --ton-0: #4a5058;
  --ton-1: #666d76;
  --ton-2: #8c939c;
  --ton-3: #b6bcc3;
  --ton-4: #eceef0;
  --shadow:    0 1px 2px rgba(0,0,0,.4), 0 10px 30px -14px rgba(0,0,0,.7);
  --shadow-lg: 0 30px 70px -24px rgba(0,0,0,.85);
  /* Im Dunkelmodus liegt der Seitengrund tiefer als die Leisten — sie heben
     sich dadurch ab, statt mit dem Grund zu verschmelzen. */
  --bar-bg:    #1b1f24;
  --bar-ink:   #eceef0;
  --bar-muted: #8d949c;
  --bar-line:  #343a41;
  --ch-red:    #cf1730;
  color-scheme: dark;
}

*, *::before, *::after { box-sizing: border-box; }

/* Muss vor allen display-Regeln stehen — Klassen wie .drawer setzen display,
   was das hidden-Attribut sonst überschreiben würde. */
[hidden] { display: none !important; }

html { scroll-behavior: smooth; height: 100%; }

/* Genau EINE vertikale Scrollleiste: das Fenster selbst scrollt nicht,
   gescrollt wird ausschliesslich der Bereich .scroll. Nur so bleiben
   Tabellenkopf, erste Spalte und Gruppentitel klebend. */
body {
  margin: 0; height: 100%; overflow: hidden;
  display: flex; flex-direction: column;
  background: var(--bg);
  color: var(--ink);
  font-family: "Inter", "Helvetica Neue", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  font-size: 15.5px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  font-feature-settings: "kern" 1, "liga" 1, "tnum" 1;
}

h1, h2, h3 { line-height: 1.15; letter-spacing: -.02em; margin: 0; font-weight: 620; }
p { margin: 0; }
a { color: inherit; text-decoration-color: color-mix(in srgb, var(--accent) 55%, transparent); text-underline-offset: 3px; }
a:hover { color: var(--accent); }
button, input, select { font: inherit; color: inherit; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 20px; }

/* Für Screenreader und die Dokumentgliederung da, sichtbar nicht. Nicht
   display:none — das nähme das Element auch der Sprachausgabe. */
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ---------------------------------------------------------------- header */
/* Spalte, damit die Fusszeile bei kurzem Inhalt nach unten gedrückt wird:
   main nimmt mit flex 1 0 auto den freien Rest, die Fusszeile schliesst daran
   an. Sie klebt NICHT — bei langem Inhalt scrollt sie mit und ist erst am Ende
   zu sehen. Bei kurzem Inhalt steht sie trotzdem am unteren Rand, statt in der
   Seitenmitte zu hängen. */
.scroll {
  flex: 1 1 auto; overflow: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column;
}
.scroll > main { flex: 1 0 auto; }
/* Inhalte unter der Tabelle bleiben beim waagrechten Scrollen an ihrem Platz */
.below { position: sticky; left: 0; }

/* DASSELBE FUER ALLES UM DIE TABELLE HERUM — siehe die Regeln bei 900px.
   Gescrollt wird nicht die Tabelle, sondern der ganze Bereich .scroll; beim
   Wischen nach links wandern Titel, tagline, Querverweisband und Legende mit
   hinaus, waehrend die Tabelle daneben stehenbleibt. Oben klafft dann eine
   leere Flaeche. */

/* Deckende dunkle Fläche. Der frühere backdrop-filter ist entfallen: bei einer
   deckenden Farbe hat er keine Wirkung und kostet nur Rechenzeit. */
.site-head {
  flex: none; z-index: 40; height: var(--head-h);
  background: var(--bar-bg); color: var(--bar-ink);
  border-bottom: 1px solid var(--bar-line);
}
.head-inner { display: flex; align-items: center; gap: 22px; height: var(--head-h); }
.brand { display: flex; align-items: center; gap: 9px; text-decoration: none; font-weight: 640; letter-spacing: -.02em; flex: none; }
.brand:hover { color: inherit; }
.brand-mark { width: 19px; height: 19px; background: var(--accent); border-radius: 3px; position: relative; flex: none; }
.brand-mark span { position: absolute; background: var(--accent-ink); border-radius: 1px; }
.brand-mark span:nth-child(1) { left: 8px; top: 3.5px; width: 3px; height: 12px; }
.brand-mark span:nth-child(2) { left: 3.5px; top: 8px; width: 12px; height: 3px; }
.brand-text em { font-style: normal; color: var(--bar-muted); font-weight: 500; }

/* Bereichsnavigation — seit dem 29. Juli 2026 IN GEBRAUCH: sie trägt das Menü
   über alle Kategorien (catMenu in build.js). Vorher stand darin ein nackter
   Pfeil auf die Startseite, und das war der einzige Weg von einer Kategorie fort.
   flex:none ist Absicht: schrumpfbar gemacht, verschwand die Navigation bei
   390px lautlos auf Breite 0. Ein fehlender Navigationspunkt ist schlimmer als
   ein sichtbarer Umbruch — auf schmalen Schirmen weicht darum die Beschriftung
   und nicht die Navigation. */
/* Kein position: relative — das Feld hängt an .dd (position: relative dort) und
   damit am Wähler selbst, wie beim Sprachwechsler. */
.site-nav { display: flex; gap: 4px; flex: none; }

/* DIE REGELN DES KATEGORIENMENUES STEHEN NICHT HIER, sondern unten bei den
   Wählern, gleich nach .dd-lang. Sie standen an dieser Stelle und wurden
   sämtlich überschrieben: .dd-cats > summary und .dd > summary haben DIESELBE
   Spezifität (zwei Klassen bzw. eine Klasse und ein Element), und bei
   Gleichstand gewinnt die spätere Regel. Also gewannen die weisse Fläche, die
   13.5px, die 6/30/6/13-Polsterung und der helle Rahmen aus .dd > summary —
   deshalb stach der Wähler auf der dunklen Leiste als heller Fleck heraus,
   obwohl hier background: none stand. Dasselbe traf .dd-panel-cats gegen
   .dd-panel (272px Mindestbreite, 7px Polsterung, 1px Abstand).
   Derselbe Fallstrick ist im Stilblatt schon zweimal vermerkt: bei .chside
   (zwei Klassen im Wähler) und bei .wrap-full.layout. Reihenfolge ist hier
   Bedeutung. */
@media (max-width: 660px) {
  /* Die BESCHRIFTUNG weicht, nicht die Navigation. Bis zum 29. Juli 2026 wurde
     hier die ganze Navigation ausgeblendet und auf Kategorieseiten durch eine
     zweite Leiste unter dem Kopf ersetzt — auf Produktseiten blieb damit unter
     660px keine. */
  .cats-lab { display: none; }
  .head-inner { gap: 12px; }
}

/* ENGER GESETZT, BEVOR ETWAS WEICHT. Auf der Kategorieseite traegt die Leiste
   ein Teil mehr als anderswo — den Filterschalter. Mit dem laengeren Namen
   «KMU Softwarevergleich» und dem Kategorienmenue mass ihr Inhalt bei 390px
   Bildschirm 410px, und «DE» stand halb ausserhalb.
   Vier Abstaende zu je 12px sind 48px; auf 7px sind es 28. Die 20px, die
   fehlten, kommen damit aus den Zwischenraeumen und nicht aus dem Inhalt —
   niemand vermisst drei Pixel Luft, aber jeder vermisst den Sprachwechsler. */
@media (max-width: 430px) {
  .head-inner { gap: 7px; }
  .head-fp { padding: 4px 7px; }
  /* Danach fehlten bei 390 und 393 noch fuenf Pixel — und das sind die
     verbreitetsten Telefonbreiten ueberhaupt. Sie kommen aus dem Schriftgrad
     der Wortmarke statt aus ihrem Verschwinden: einen Namen kleiner zu setzen
     ist ein Nachgeben, ihn wegzunehmen ein Verzicht. Der Abstand zum Zeichen
     gibt den Rest. */
  .brand { gap: 7px; }
  .brand-text { font-size: 14.5px; }
}

/* Ganz schmal: die Wortmarke weicht, das Zeichen bleibt und führt weiter
   auf die Startseite. Gemessen wird sie erst unter 384px zum Problem. */
@media (max-width: 384px) {
  .brand-text { display: none; }
}

.head-meta { display: flex; gap: 18px; font-size: 12.5px; color: var(--bar-muted); }
.head-meta b { color: var(--bar-ink); font-weight: 640; }
.head-meta span::before { content: ""; }
/* flex: 0 1 auto statt none, damit das Suchfeld auf engen Schirmen schrumpfen
   kann statt die Leiste zu sprengen. */
.head-right { display: flex; align-items: center; gap: 10px; margin-left: auto; flex: 0 1 auto; min-width: 0; }

/* Suche in der Kopfleiste. Das Feld sitzt auf dunklem Grund und nutzt darum die
   bar-Variablen; die Trefferliste liegt als Fläche DARÜBER und nutzt die
   Seitenpalette — sonst wäre sie im Hellmodus dunkel und stäche heraus. */
.hsearch { position: relative; flex: 0 1 210px; min-width: 0; }
/* Lupe im Feld: reines Markup statt Bilddatei, damit sie die Schriftfarbe
   mitnimmt und beim Fokus mit aufhellt. pointer-events aus, sonst schluckt sie
   den Klick, der ins Eingabefeld gehört. */
.hs-ico {
  position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
  width: 12px; height: 12px; pointer-events: none;
  fill: none; stroke: var(--bar-muted); stroke-width: 1.7; stroke-linecap: round;
  transition: stroke .14s;
}
.hsearch input {
  -webkit-appearance: none; appearance: none;
  width: 100%; padding: 4px 13px 4px 30px; line-height: 1.5; border-radius: 999px;
  font-size: 12.5px; color: var(--bar-ink);
  background: color-mix(in srgb, var(--bar-ink) 7%, transparent);
  border: 1px solid var(--bar-line);
  transition: background-color .14s, border-color .14s;
}
.hsearch input::placeholder { color: var(--bar-muted); }
.hsearch input::-webkit-search-decoration,
.hsearch input::-webkit-search-cancel-button { -webkit-appearance: none; }
.hsearch input:hover { border-color: color-mix(in srgb, var(--bar-ink) 30%, transparent); }
.hsearch input:focus {
  outline: none; border-color: color-mix(in srgb, var(--bar-ink) 45%, transparent);
  background: color-mix(in srgb, var(--bar-ink) 12%, transparent);
}
.hsearch input:focus ~ .hs-ico,
.hsearch:hover .hs-ico { stroke: var(--bar-ink); }
.hs-panel {
  position: absolute; top: calc(100% + 7px); right: 0; z-index: 70;
  min-width: 280px; max-width: min(380px, calc(100vw - 40px));
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line-hard);
  border-radius: var(--r-s); box-shadow: var(--shadow-lg);
  padding: 4px; display: grid; gap: 1px;
  max-height: calc(100vh - 80px); overflow-y: auto;
}
.hs-item {
  display: flex; flex-direction: column; gap: 1px; padding: 7px 10px;
  border-radius: 4px; text-decoration: none; color: inherit;
}
.hs-item:hover, .hs-item.on { background: var(--surface-2); color: inherit; }
.hs-n { font-size: 13.5px; font-weight: 560; }
.hs-s { font-size: 11.5px; color: var(--muted); }
.hs-none { padding: 9px 10px; font-size: 12.5px; color: var(--muted); }
/* Letzte Zeile im Aufklappen: führt auf die Ergebnisseite. Abgesetzt, weil sie
   etwas anderes tut als ein Treffer — sie springt nicht, sie zeigt alle. */
.hs-all {
  display: block; margin-top: 3px; padding: 8px 10px; border-radius: 4px;
  border-top: 1px solid var(--line); font-size: 12px; font-weight: 560;
  color: var(--muted); text-decoration: none;
}
.hs-all:hover, .hs-all.on { background: var(--surface-2); color: var(--accent); }

/* ------------------------------------------------------------ Ergebnisseite */
.res-h { margin-top: 30px; }
.res-h:first-child { margin-top: 22px; }
.res-grid { display: grid; gap: 0; margin-top: 6px; }
.res-item {
  display: flex; flex-direction: column; gap: 2px;
  padding: 12px 0 13px; border-top: 1px solid var(--line);
  text-decoration: none; color: inherit;
}
a.res-item:hover .res-n { color: var(--accent); }
.res-n { font-size: 15px; font-weight: 620; }
.res-s { font-size: 13px; color: var(--muted); }
/* Ein Produkt steht in mehreren Kategorien — jede als eigener Verweis */
.res-cats { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 5px; }
.res-cats a {
  padding: 3px 10px; border-radius: 999px; font-size: 12.5px;
  border: 1px solid var(--line-hard); text-decoration: none; color: var(--ink);
}
.res-cats a:hover { border-color: var(--accent); color: var(--accent); }
.res-hint { margin-top: 20px; font-size: 14px; color: var(--muted); }

/* SCHMALE KOPFLEISTE: das Suchfeld wird zur Lupe und faehrt beim Antippen aus.
   Bei 130px Grundbreite stand «Produkt suchen» abgeschnitten als «Produkt
   suchei» da — und auf der Kategorieseite, wo der Filterschalter mitzehrt,
   schrumpfte es unbeabsichtigt bis zur Unkenntlichkeit. Dieselbe Leiste sah
   also je nach Seite anders aus, ohne dass es so gemeint war.

   Als Lupe ist die Verkleinerung eine Entscheidung statt eines Unfalls, und die
   Breite steht fest. Das Feld bleibt dabei ein echtes Eingabefeld mit
   aria-label — es wird nur schmal, nicht zum Knopf, und braucht darum kein
   Skript.

   Die Platzhalterschrift wird durchsichtig statt entfernt: entfernt waere sie
   beim Ausfahren weg, und man saehe ein leeres Feld ohne Angabe, was
   hineingehoert. */
@media (max-width: 660px) {
  .hsearch { flex: 0 0 34px; transition: flex-basis .18s ease-out; }
  .hsearch input { padding-right: 8px; }
  .hsearch input::placeholder { color: transparent; }
  .hsearch:focus-within { flex-basis: min(230px, 56vw); }
  .hsearch:focus-within input::placeholder { color: var(--bar-muted); }

  /* Tippflaechen: mit 4px Polsterung war das Feld 29px hoch. Auf einem
     Bildschirm, der mit dem Finger bedient wird, ist das zu knapp — 36px sind
     das Mindeste, das sich sicher treffen laesst. */
  .hsearch input, .dd-lang > summary { min-height: 36px; }
  .hsearch input { padding-top: 0; padding-bottom: 0; }
  .dd-lang > summary { display: inline-flex; align-items: center; }
}

/* Der Umschalter Hell/Dunkel ist im Juli 2026 entfallen. Die dunkle Palette
   oben bleibt in Gebrauch — sie wird aus der Systemeinstellung gesetzt
   (prefers-color-scheme), nicht mehr von Hand gewählt. */

/* Die Bedienleiste (.controls / .ctrl-inner / .ctrl-row / .ctrl-label) ist am
   29. Juli 2026 entfallen. Ihr Inhalt ist gewandert: «Ich bin» und «Sortieren
   nach» ins Filterpanel, der Panelschalter in die schwarze Kopfzeile, Trefferzahl
   und Schweiz-Wähler in den Seitenkopf (.cat-head weiter unten). Übrig war ein
   weisser Streifen mit Haarlinie um eine vierstellige Zahl. */

/* Die zwei Hauptfilter «Ich bin» und «Ich suche» als Dropdown */
/* ================= Filterpanel links, Tabelle rechts =================

   Nach der Bauform, die Facettenfilter überall haben — und die meine erste
   Fassung verfehlte:

     KÄSTCHEN LINKS, Beschriftung, ZAHL RECHTSBÜNDIG. Eine Zeile, eine Höhe.
     Die Zahlen stehen in einer festen Spalte und bilden dadurch eine lesbare
     Kolonne; das ist der Unterschied zwischen «aufgeräumt» und «zusammengewürfelt».
     Vorher standen Beschriftung UND Zahl links und dahinter zwei Textpillen —
     bei 264px Panelbreite brach damit jede Zeile um.

     Das Kästchen ist «zwingend»: ein Kästchen filtert, das ist die Erwartung.
     «wichtig» ist ein kleiner Nebenschalter am rechten Rand — es sortiert nur
     und darf die Zeile nicht dominieren. Was beide tun, sagt der Hinweis über
     der Liste, einmal statt vierzehnmal.

   Breite 300px: bei deutschen Beschriftungen tragen 240px nicht. Gemessen am
   längsten Fall bleiben so 180px für den Text — genug für eine Zeile.
   ---------------------------------------------------------------------- */
/* ZWEI Klassen im Wähler, nicht eine. .wrap-full setzt padding: 0, damit die
   Tabelle bis an den Rand scrollen kann — und es steht WEITER UNTEN im Stilblatt.
   Bei gleicher Spezifität gewinnt die spätere Regel, also verlor der linke
   Aussenabstand des Panels jedes Mal. Mit .wrap-full.layout sind es zwei Klassen
   gegen eine, und die Reihenfolge im Blatt spielt keine Rolle mehr. */
.wrap-full.layout {
  /* 196px: die Beschriftungen sind auf kurze Wörter gekürzt, und seit die Zahl
     dem Text folgt statt am rechten Rand zu stehen, braucht die Spalte keine
     Breite mehr für Leerraum zwischen beiden. Der Platz gehört der Tabelle. */
  /* Die Panelbreite als Variable: die Kleberegeln weiter unten brauchen sie,
     und sie wechselt mit dem Panelzustand. */
  --fp: 196px;
  display: grid; grid-template-columns: var(--fp) minmax(0, 1fr);
  /* KEIN Abstand zwischen Panel und Tabelle und keiner links davor: die weisse
     Flaeche laeuft von der linken Fensterkante bis nach rechts durch, geteilt von
     einer einzigen Haarlinie. Mit Abstaenden zeigte sich der graue Untergrund als
     senkrechte Streifen zwischen Panel, Leiste und Tabelle — drei Zonen, wo eine
     Arbeitsflaeche sein soll. Der Aussenabstand steckt jetzt IM Panel. */
  gap: 0;
  /* stretch statt start: das Panel reicht damit von der Bedienleiste bis zum
     unteren Ende der Tabelle, und seine Trennlinie läuft über die ganze Höhe.
     Mit «start» endete es dort, wo sein Inhalt endete. */
  align-items: stretch;
  /* Links derselbe Aussenabstand wie bei jedem anderen Inhalt (.wrap hat 20px).
     Rechts bleibt es bei 0: dort ist die Tabelle, und die soll die Breite
     behalten. */
  padding-left: 0;
}
/* minmax(0, 1fr) statt 1fr: sonst zieht die breite Tabelle das Raster auf und
   das Panel wird gequetscht. */
.wrap-full.layout.fp-zu { --fp: 0px; grid-template-columns: var(--fp) minmax(0, 1fr); gap: 0; padding-left: 0; }
.layout.fp-zu .fpanel { display: none; }
/* KATEGORIE OHNE LINKE SPALTE (categories.js: panel: false). Anders als fp-zu
   ist das kein Zustand, den der Besucher umschaltet, sondern eine Eigenschaft
   der Seite — darum eine eigene Klasse und keine Voreinstellung von fp-zu. Sie
   steht schon im ausgelieferten Quelltext, damit die Leiste beim Laden nicht
   kurz aufblitzt. EINE Spalte statt einer auf 0px geschrumpften: ein
   Rasterplatz mit Breite 0 bleibt ein Rasterplatz und erbt den Spaltenabstand. */
.wrap-full.layout.nopanel { --fp: 0px; grid-template-columns: minmax(0, 1fr); gap: 0; padding-left: 0; }
.layout.nopanel .fpanel { display: none; }
/* --fp AUF NULL, und das ist der Teil, den man leicht vergisst: die Variable
   beschreibt nicht nur die Breite der Spalte, sondern wird weiter unten fuer den
   klebenden Kopf und das Querverweisband GERECHNET — «width: calc(var(--vis) -
   var(--fp))» und «left: var(--fp)». Ohne diese Zeile blieb rechts ein Streifen
   von genau 196px leer, weil die Seite Platz fuer eine Leiste abzog, die es
   nicht mehr gibt. Gesehen im Bild, nicht im Quelltext. */

/* KEIN eigener Scrollbereich und kein sticky. Beides war der Versuch, die Filter
   im Bild zu halten — und beides kostete mehr, als es brachte: eine zweite
   Scrollleiste neben der der Seite, und ein Panel, das mitten in der Tabelle
   endete. Jetzt ist es eine volle Spalte, die mit der Seite scrollt. */
/* WEISS wie die Bedienleiste und die Tabelle: die drei bilden EINE Arbeitsflaeche
   auf dem grauen Untergrund der Seite, getrennt nur durch die Haarlinie.
   --surface-2 (#fafaf8) waere die Alternative, liegt aber nur zwei Prozent neben
   Weiss — das liest sich als Versehen, nicht als Entscheidung. Und es ist die
   Farbe des Zeilen-Hovers: als Grundfarbe des Panels waere der Hover unsichtbar.
   Weiss als Grund und #fafaf8 im Hover ist die einzige Kombination, in der beides
   funktioniert. */
.fpanel {
  min-width: 0; background: var(--surface);
  border-right: 1px solid var(--line);
}
.fpanel[hidden] { display: none; }
/* LINKS 14px, nicht die 20px der uebrigen Seite: das Panel ist dicht gesetzt,
   und ein Einzug, der zum Fliesstext passt, wirkt hier als Loch. Die
   Trefferflaeche der Zeilen ragt weiterhin 6px darueber hinaus. */
.fp-scroll { padding: 12px 10px 22px 14px; }

.fp-group { margin: 0 0 14px; }
.fp-group h4 {
  margin: 0 0 3px; font-size: 9.5px; letter-spacing: .09em; text-transform: uppercase;
  font-weight: 650; color: var(--muted);
}

/* Eine Kriterienzeile. Feste Höhe, damit die Liste eine Kolonne bildet.
   Das Raster hält die Zahl und den Nebenschalter in einer Spalte — mit flex
   wanderten sie je nach Textlänge. */
/* DIE ZAHL FOLGT DEM TEXT, sie steht nicht am rechten Rand. Vorher nahm die
   Beschriftung 1fr und die Zahl sass in einer eigenen Spalte davor: bei «CRM 4»
   lagen damit 60px Leerraum zwischen Wort und Zahl, und die Zahl las sich als
   Angabe zur Spalte statt zum Kriterium. Jetzt ist die Beschriftung so breit wie
   ihr Text (max-content, aber schrumpfbar), die Zahl folgt unmittelbar, und der
   verbleibende Platz fällt DAHINTER an.
   Der Preis dafür ist bewusst: die Zahlen bilden keine ruhige Spalte mehr. Sie
   gehören zum Wort, nicht zur Liste. Nur das «!» bleibt am rechten Rand — es ist
   ein Schalter, und Schalter müssen dort liegen, wo die Hand sie erwartet. */
.fp-row {
  display: grid; grid-template-columns: minmax(0, max-content) 1fr 18px;
  align-items: center; gap: 6px; min-height: 28px;
}
/* Die Trefferflaeche ragt 6px ueber den Text hinaus, ohne ihn einzuruecken:
   der Text sitzt damit auf denselben 20px wie jeder andere Inhalt der Seite. */
.fp-row { margin: 0 -6px; padding: 0 6px; }
.fp-row:hover { background: var(--surface-2); border-radius: 4px; }
/* Die ganze Beschriftung ist Teil des Kästchens: ein Klick auf den Text setzt
   das Kriterium. Kleinere Trefferflächen sind der häufigste Mangel solcher
   Listen. */
.fp-lab {
  display: flex; align-items: center; gap: 7px; min-width: 0;
  font-size: 11.5px; line-height: 1.3; cursor: pointer; padding: 2px 0;
}
/* TINTE, nicht Akzent. Die Regel dieser Seite: Tinte heisst «gesetzt», Rot
   heisst «filtert weg». Das Kaestchen setzt (wichtig), das «!» filtert
   (zwingend) — vorher trug das Kaestchen Rot und das «!» Tinte, also genau
   verkehrt. */
.fp-lab input {
  accent-color: var(--ink); width: 14px; height: 14px; flex: none; margin: 0;
  cursor: pointer;
}
.fp-txt { min-width: 0; }
/* Gesetzt: die Beschriftung tritt hervor, ohne Farbe zu verbrauchen. */
.fp-row-imp .fp-txt { font-weight: 560; }
.fp-row-req .fp-txt { font-weight: 600; color: var(--ink); }

/* Die Zahl: so viele blieben übrig, wenn das Kriterium zwingend gesetzt wäre.
   LINKSBUENDIG, damit sie am Wort klebt und nicht am Rand — rechtsbündig war sie
   an eine Spalte gebunden, die es nicht mehr gibt. Gleichbreite Ziffern bleiben:
   sie halten die Zahl selbst ruhig, wenn sie von 9 auf 10 wechselt. */
.fp-n {
  text-align: left; font-size: 11px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* «wichtig» — Nebenschalter. Sortiert nach oben, filtert nicht. Bewusst leise:
   ein Ring, kein gefüllter Knopf, solange er aus ist. */
.fp-imp {
  width: 18px; height: 18px; padding: 0; border-radius: 50%;
  border: 1px solid var(--line-hard); background: none; color: var(--muted);
  font: inherit; font-size: 10px; font-weight: 700; cursor: pointer;
  display: grid; place-items: center; justify-self: end;
}
.fp-imp:hover { border-color: var(--ink); color: var(--ink); }
.fp-imp.on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }

/* Fuehrt ein Kriterium auf null, ist NUR «!» abgeschaltet — das Kaestchen bleibt.
   Einen Wunsch zu aeussern kann nie eine leere Tabelle ergeben, und die ganze
   Zeile auszugrauen haette diese Moeglichkeit verdeckt. Die Null traegt die
   Aussage: die Bedingung ist in diesem Bestand nicht zu haben. */
.fp-n-null { color: var(--accent); }
/* ALLE — kein Zahlenwert, also auch nicht als Zahl gesetzt. Kleiner, gesperrt
   und in Grossbuchstaben liest es sich als Vermerk und nicht als Wert, der
   in der Zahlenspalte mitzaehlt. Die Grossschreibung kommt aus dem Stilblatt
   und nicht aus den Sprachdateien: dort steht «alle», «tous», «tutti», «all»
   in natuerlicher Schreibung, und wer die Regel entfernt, bekommt lesbaren
   Text statt geschriener Wortfetzen. */
.fp-n-alle {
  font-size: 9.5px; letter-spacing: .07em; text-transform: uppercase;
  font-weight: 660; color: var(--muted);
}
.fp-imp:disabled { opacity: .35; cursor: not-allowed; }

/* Budget und «nur Unterschiede»: dieselbe Sorte Filter, darum im Panel — aber
   abgesetzt, weil sie nicht zur Kriterienliste gehören. */
.fp-block {
  margin-top: 4px; padding-top: 14px; border-top: 1px solid var(--line);
  display: grid; gap: 12px;
}


@media (max-width: 900px) {
  /* Schmale Geräte: EINE Spalte, das Panel wird ein Blatt über der Tabelle.
     Das Verdecken kommt hier zurück — unvermeidlich. Abgemildert dadurch, dass
     die Trefferzahl in der Leiste oben stehen bleibt und mitläuft. */
  /* AUCH HIER zwei Klassen: die Grundregel oben ist .wrap-full.layout, und eine
     Regel mit nur .layout käme dagegen nicht an — die 20px linker Abstand wären
     auf schmalen Geräten stehengeblieben und hätten die Tabelle verschoben. */
  .wrap-full.layout { grid-template-columns: minmax(0, 1fr); gap: 0; padding-left: 0; }
  /* DREI KLASSEN, aus demselben Grund wie eine Zeile darueber — und diesmal
     reichten zwei nicht: die Grundregel «.layout-main { padding-left: 16px }»
     steht weiter UNTEN im Stylesheet und gewinnt bei gleichem Gewicht. Die
     16px blieben also stehen, obwohl hier seit je das Gegenteil steht.
     Gemessen begann die Tabelle bei x=16, und der Kopf mit «width: 100vw» ragte
     genau um diese 16px rechts aus dem Bild: «Schweizer Besonderheiten» stand
     als «Schweizer Besonderheiter» da. */
  .wrap-full.layout .layout-main { padding-left: 0; }
  /* z-index 30: ÜBER der Tabelle (deren klebende Köpfe reichen bis 8), aber UNTER
     dem Seitenkopf. Vorher stand hier 60. Der Kopf hat 40, ist als Flex-Kind mit
     z-index aber ein eigener STAPELRAUM — alles darin, auch ein Panel mit 60 oder
     80, wird mit seinen 40 verrechnet. Das Filterblatt gewann damit gegen jedes
     aufgeklappte Kopfmenü: das Kategorienblatt war unter 900px ab etwa 40 Prozent
     Fensterhöhe verdeckt, der Sprachwechsler ebenso. Im Sichttest bei 380px
     gesehen. Eine höhere Zahl am Panel hilft nicht, nur eine kleinere hier. */
  .fpanel {
    position: fixed; inset: auto 0 0 0; z-index: 30; background: var(--surface);
    border-right: 0; border-top: 1px solid var(--line-hard);
    box-shadow: 0 -8px 24px rgba(0, 0, 0, .16);
    border-radius: var(--r) var(--r) 0 0;
  }
  /* Das Blatt MUSS innen scrollen — es liegt fest am unteren Rand und kann nicht
     wachsen. Das ist der Unterschied zur Spalte auf breiten Schirmen, die mit der
     Seite scrollt und darum keine eigene Leiste braucht.

     dvh und nicht vh: auf dem Telefon faehrt die Adressleiste beim Scrollen ein
     und aus, und vh rechnet mit dem GROESSTEN Stand. Das Blatt waere in dem
     Moment hoeher als der sichtbare Bereich, und seine unterste Zeile laege
     unter dem Bildschirmrand. */
  .fp-scroll {
    max-height: 62vh; max-height: 62dvh; overflow-y: auto; overscroll-behavior: contain;
    padding: 16px 18px 22px;
  }
  .wrap-full.layout.fp-zu { grid-template-columns: minmax(0, 1fr); }

  /* UMGEKEHRTE VORGABE AUF SCHMALEN GERAETEN: das Blatt ist zu, bis es
     ausdruecklich offen ist. Auf breiten Schirmen gilt weiter das Gegenteil —
     dort steht das Panel als Spalte neben der Tabelle und verdeckt nichts, hier
     legt es sich darueber.

     Das kostet die Regel nichts an Zugaenglichkeit: ohne JavaScript filtert das
     Panel nicht, ein sichtbares waere also ein Angebot, das nicht eingeloest
     wird — und es laege ueber dem einzigen Inhalt, den die Seite ohne Skript
     wirklich hat. */
  .fpanel { display: none; }
  .layout.fp-auf .fpanel { display: block; }

  /* ---- Was beim Seitwaertswischen stehenbleiben muss ---------------------
     Auf dem Telefon ist jede zweite senkrechte Wischbewegung ein wenig schraeg.
     Gemessen wanderten Titel, tagline, Querverweisband und Legende dabei um
     700px hinaus, und oben blieb eine leere Flaeche stehen — waehrend die
     Tabelle daneben ordentlich weiterlief.

     WARUM «position: sticky» ALLEIN NICHT REICHTE: ein klebendes Element kann
     seinen eigenen Container nicht verlassen. .layout-main und main sind nur
     so breit wie die Spalte (375px) und wandern selbst mit; ein Kind darin
     bleibt hoechstens innerhalb dieser 375px stehen. Deshalb steht dieselbe
     Absicht in .below schon lange im Stylesheet, ohne je gewirkt zu haben.

     Die Gruppentitel der Tabelle kleben dagegen richtig — ihr Container ist
     eine Zelle mit colspan ueber die ganze Tabellenbreite. Genau das wird hier
     nachgebaut: die Huellen bekommen Inhaltsbreite, die klebenden Elemente
     darin werden auf die Bildschirmbreite begrenzt.

     Nicht geaendert wird, WER scrollt. Dass der ganze Bereich .scroll scrollt
     und nicht die Tabelle, ist die Bedingung dafuer, dass Tabellenkopf und
     erste Spalte kleben — und zu wissen, welcher Anbieter in der Spalte steht,
     wiegt auf einem Telefon schwerer als ein Titel, der stehenbleibt.

     NUR .layout-main, NIEMALS main ODER .view. Beide gibt es auf jeder Seite.
     «width: max-content» auf main gab der Startseite ihre Desktop-Breite von
     1240px zurueck — und weil body den Ueberlauf abschneidet, brach dort nichts
     mehr um, es war schlicht abgeschnitten. .layout-main steht nur auf
     Kategorie- und Kriterienseiten, also genau dort, wo eine breite Tabelle
     ueberhaupt vorkommt.

     Was UNTER der Tabelle steht — Legende, Verweis auf den Marktbefund —
     bleibt damit ungeschuetzt und wandert weiter mit. Das ist der bewusste
     Rest: es liegt ausserhalb von .layout-main, und dafuer muesste die Huelle
     der ganzen Seite verbreitert werden. Am oberen Rand, der immer im Bild ist,
     wiegt es schwer; unterhalb der Tabelle scrollt man ohnehin zurueck. */
  .layout-main { width: max-content; min-width: 100%; }
  .cat-head, .querband { position: sticky; left: 0; width: 100vw; max-width: 100vw; }
}

/* ========== WAAGRECHT SCROLLEN, OHNE DEN SEITENKOPF ZU VERLIEREN ==========
   Die Vergleichstabelle wird bis zu 6500px breit, .scroll rollt sie waagrecht.
   Seitenkopf, Trefferzeile und Querverweisband wanderten dabei mit hinaus: bei
   Spalte 20 stand ueber der Tabelle ein leeres graues Band, und man wusste
   nicht mehr, welche Kategorie man vor sich hat. Fuer schmale Geraete ist das
   behoben (Regel weiter oben, max-width 900px), fuer den Desktop war es offen.

   DIE REGEL, DIE ALLES ERKLAERT: ein klebendes Element verlaesst niemals seinen
   Container. Der Tabellenkopf klebt, weil die Tabelle ueber die ganze
   Scrollbreite reicht; der Seitenkopf klebte nicht, weil .layout-main nur so
   breit war wie das Fenster — er scrollte mitsamt seinem Container weg.
   Darum wird hier der CONTAINER verbreitert und nicht eine Regel gestapelt.
   Genau denselben Griff macht die Mobilregel oben, mit derselben Begruendung.

   left: var(--fp) und nicht 0: links steht die Filterspalte. Sie ist ein
   Rasterfeld und kann selbst nicht kleben — ein Rasterfeld ist der Bezugsrahmen
   seines Inhalts. Sie scrollt also weg, und der Kopf klebt dort, wo sie war.
   Der negative Aussenabstand gleicht die 16px Polsterung von .layout-main aus,
   damit der Kasten buendig beginnt; die Innenpolsterung haelt den TEXT an
   derselben Stelle wie zuvor. */
@media (min-width: 901px) {
  .layout-main { width: max-content; min-width: 100%; }
  /* MIT DEM RASTERWAEHLER DAVOR: .querband traegt weiter unten im Blatt eine
     eigene Regel mit «left: 0» und gleicher Spezifitaet — bei Gleichstand
     gewinnt die spaetere, und das Band klebte am linken Fensterrand statt hinter
     der Filterspalte. Zwei Klassen heben diese Regel darueber. */
  .wrap-full.layout .cat-head, .wrap-full.layout .querband {
    position: sticky; left: var(--fp, 196px); z-index: 5;
    box-sizing: border-box;
    /* --vis ist die SICHTBARE Breite des Rollbereichs, in app.js gemessen.
       100vw waere falsch: es zaehlt die senkrechte Bildlaufleiste mit, und der
       Kopf ragte um deren Breite unter sie — «Schweizer Besonderheiten» am
       rechten Ende wurde angeschnitten. Der Rueckfall auf 100vw greift nur,
       solange noch nicht gemessen wurde. */
    width: calc(var(--vis, 100vw) - var(--fp, 196px));
    margin-left: -16px;
  }
  /* Rechts derselbe Abstand wie links. Die Grundregel gibt dem Kopf 4px nach
     rechts — das genuegte, solange er im Fluss lag und rechts ohnehin der
     Seitenrand kam. Seit er klebt und bis an die sichtbare Kante reicht, sass
     «Schweizer Besonderheiten» direkt am Rand. */
  .wrap-full.layout .cat-head { padding-left: 20px; padding-right: 20px; }
  .wrap-full.layout .querband-in { padding-left: 30px; }
}

/* Die rechte Spalte: Bedienleiste, Querverweisband, Tabelle. min-width: 0, sonst
   zieht die breite Tabelle das Raster auf. Weiss kommt von den Teilen selbst —
   die Bedienleiste bringt es mit, die Tabellenzellen ebenfalls. */
/* WEISS wie das Panel, und mit einem Abstand zur Haarlinie: 16px Luft zwischen
   Trennlinie und Tabelle. Die Polsterung MUSS an einer weissen Flaeche haengen —
   an .layout oder .layout-main ohne Hintergrund zeigte sie den grauen Untergrund,
   und der senkrechte Streifen waere zurueck, den die durchgehende Flaeche gerade
   beseitigt hat. */
.layout-main {
  min-width: 0; background: var(--surface); padding-left: 16px;
}
/* KOPF DER KATEGORIESEITE — und der einzige Kopf, den die rechte Spalte hat,
   seit die Bedienleiste entfallen ist. Zwei Zeilen links, ein Wähler rechts:

     Buchhaltungssoftware                    [+ Schweizer Besonderheiten]
     Fibu, MWST und Abschluss — …   ·  14/29 gezeigt

   align-items: flex-start setzt den Wähler auf die Höhe des TITELS und nicht auf
   die Mitte des zweizeiligen Blocks — mittig ausgerichtet sass er zwischen den
   Zeilen und wirkte wie zur tagline gehörend. overflow bleibt sichtbar: das
   Panel des Wählers fährt darüber hinaus.
   Kompakt gesetzt — das ist Ortsangabe und kein Vorspann, der Platz gehört der
   Tabelle. Der Einzug ergänzt die 16px der Spalte auf 20px von der Haarlinie. */
.cat-head {
  padding: 13px 4px 11px; display: flex; align-items: flex-start; gap: 14px;
  overflow: visible; border-bottom: 1px solid var(--line);
}
.cat-head-t { min-width: 0; flex: 1 1 auto; }
.cat-head h1 {
  margin: 0; font-size: 17px; line-height: 1.25; letter-spacing: -.015em;
  font-weight: 650; color: var(--ink);
}
/* Zweite Zeile: tagline und Trefferzahl teilen sie sich. baseline, damit die
   beiden Schriftgrössen auf einer Linie sitzen; der Trenner gehört zur Zahl,
   damit er mit ihr verschwindet, wenn die Zeile umbricht. */
.cat-sub {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  margin: 3px 0 0; min-width: 0;
}
.cat-lead {
  margin: 0; font-size: 12.5px; line-height: 1.45; color: var(--muted);
  max-width: 78ch;
}
.cat-sub .result-count {
  white-space: normal; overflow: visible; max-width: none; flex: none;
}
.cat-sub .result-count::before {
  content: '·'; margin-right: 8px; color: var(--line-hard);
}
/* Vor dem ersten Zeichnen ist die Zahl leer — dann darf auch der Trenner nicht
   dastehen. Ohne :empty stand auf jeder Seite ohne JavaScript ein einzelner
   Punkt hinter der tagline. */
.cat-sub .result-count:empty::before { content: none; }
@media (max-width: 660px) {
  .cat-head { padding: 12px 14px 10px; gap: 10px; }
  .cat-head h1 { font-size: 15.5px; }
  .cat-lead { font-size: 12px; }
  /* Der Wähler faellt unter den Titel. Nebeneinander blieben dem Titel rund
     120px, und «Buchhaltungssoftware Schweiz — Vergleich» stand dreizeilig in
     einer Schmalspalte neben einem einzeiligen Knopf — der Knopf sah aus wie
     die Ueberschrift. Eine Zeile Hoehe ist auf dem Telefon billiger als ein
     zerrissener Titel; das Kuerzen des Knopfes auf das blosse Kreuz waere
     billiger gewesen und haette ein Zeichen ohne Wort hinterlassen. */
  .cat-head { flex-wrap: wrap; }
  .cat-head-t { flex: 1 1 100%; }
  .chside { margin-left: 0; }
}

/* Schalter fürs Filterpanel in der schwarzen Kopfleiste. Zeigt auf die
   bar-Variablen, nicht auf die Seitenpalette — sonst wäre er dort unlesbar,
   dieselbe Regel wie beim Sprachwechsler. */
.head-fp {
  flex: none; border: 1px solid transparent; background: none;
  color: var(--bar-muted); border-radius: var(--r-s);
  font: inherit; font-size: 11.5px; font-weight: 620; letter-spacing: .05em;
  padding: 4px 9px; cursor: pointer; white-space: nowrap;
  transition: color .14s, border-color .14s;
}
.head-fp:hover, .head-fp[aria-expanded="false"] {
  color: var(--bar-ink); border-color: var(--bar-line);
}

/* Voreinstellungen: dieselbe Zeile wie die Kriterien, nur ohne die dritte
   Spalte — es gibt bei ihnen kein «verstaerken». */
.fp-lab input[type="radio"] { accent-color: var(--ink); }
/* Die Reihe ist kein eigener Block mehr, sondern die erste Gruppe im Panel. */

/* JEDER Waehler im Kopf des Filterpanels — «Ich bin» und «Sortieren nach».
   Eine Regel fuer beide statt zwei fast gleiche: NUR das Noetige: volle Breite und ein
   Abstand nach unten. Die Zusammenfassung bleibt beim Muster der uebrigen vier
   Waehler (Beschriftung und Wert nebeneinander, inline-flex) — eigene Regeln fuer
   display und Richtung waren ein fuenftes Muster fuer dasselbe Element und die
   erste Stelle, an der ein Fehler entsteht. */
.fpanel .dd { display: block; margin: 0 0 14px; }
.fpanel .dd[hidden] { display: none; }
.fpanel .dd > summary { width: 100%; padding: 5px 24px 5px 11px; }
/* Im schmalen Panel kleiner als in der Leiste oben — dort ist mehr Platz. */
.fpanel .dd .dd-key { font-size: 9.5px; }
.fpanel .dd .dd-val { font-size: 12px; max-width: 100%; }
.fpanel .dd .dd-pick { font-size: 12px; gap: 10px; }
/* Das aufklappende Feld nimmt die Breite der Spalte, nicht 272px wie in der
   Leiste oben — dort ist mehr Platz. */
.fpanel .dd .dd-panel { left: 0; right: 0; min-width: 0; }
/* FLACHE Fassung: kein Kasten, kein Hintergrund, klein. Fuer eine
   Feineinstellung, die nicht so schwer wiegen darf wie «Ich bin» — und die
   trotzdem ihren Wert nennt, was ein Icon nicht koennte. */
.fpanel .dd-flach > summary {
  border: 0; background: none; border-radius: 0; min-height: 0;
  padding: 1px 15px 1px 0; gap: 5px;
}
.fpanel .dd-flach > summary::after { right: 2px; width: 5px; height: 5px; }
.fpanel .dd-flach > summary:hover { border-color: transparent; }
.fpanel .dd-flach > summary:hover .dd-val { color: var(--ink); }
.fpanel .dd-flach .dd-key { font-size: 9px; }
.fpanel .dd-flach .dd-val { font-size: 11px; font-weight: 500; color: var(--muted); }
.fpanel .dd-flach .dd-panel { left: 0; right: auto; min-width: 180px; }

/* Die Zahl rechts, wie in den Kriterienzeilen. */
.fpanel .dd .dd-pick small { margin-left: auto; }

.dd { position: relative; flex: none; }
/* EINE Radius-Familie: alles, was man anklickt und was in einer Zeile sitzt, ist
   vollrund — Wähler, Suchfeld, Pillen, Etiketten. Alles, was eine Fläche ist,
   hat 6px: Panels, Kacheln, Tabellen. Vorher hatten die Wähler 6px und lasen
   sich dadurch wie Flächen statt wie Bedienelemente. */
.dd > summary {
  list-style: none; cursor: pointer; user-select: none;
  /* EIN Hintergrund für alle Wähler. Vorher trugen die Primärfilter --surface und
     die übrigen --surface-2 — zwei Töne in einer Zeile, ohne dass der Unterschied
     etwas bedeutete. Der Sprachwechsler in der dunklen Kopfleiste bleibt davon
     unberührt: er setzt background eigens auf none. */
  border: 1px solid var(--line-hard); background: var(--surface);
  padding: 6px 30px 6px 13px; border-radius: 999px; font-size: 13.5px;
  display: inline-flex; align-items: baseline; gap: 8px; white-space: nowrap; position: relative;
}
/* Die Mindesthöhe von 30px hing an .ctrl-row und ist mit der Bedienleiste
   entfallen. Sie glich dort zwei Wähler mit verschiedener Schriftgrösse aus; im
   Seitenkopf steht nur noch einer, und der bringt seine Höhe selbst mit. */
.dd > summary::-webkit-details-marker { display: none; }
/* Der Pfeil wird GERECHNET zentriert, nicht mit einem festen top gesetzt. Ein
   Zahlenwert stimmt nur für eine Höhe — die Wähler sind aber unterschiedlich
   hoch (dd-key + dd-val gegen einzeilige Umschalter), und der Pfeil sass
   dadurch zu tief. top/bottom: 0 mit margin: auto zentriert ein absolut
   gesetztes Element mit bekannter Höhe von selbst.

   Das translate im transform ist die optische Korrektur: der Pfeil ist ein
   gedrehtes Quadrat, dessen Tinte nur auf zwei Kanten liegt — geometrisch
   zentriert wirkt er versetzt. -1px/-1px im gedrehten Koordinatensystem
   entspricht in beiden Zuständen genau 1.4px senkrecht zur Spitze. */
.dd > summary::after {
  content: ""; position: absolute; right: 12px; top: 0; bottom: 0; margin: auto 0;
  width: 6px; height: 6px;
  border-right: 1.5px solid var(--muted); border-bottom: 1.5px solid var(--muted);
  transform: rotate(45deg) translate(-1px, -1px); transition: transform .16s;
}
.dd[open] > summary::after { transform: rotate(-135deg) translate(-1px, -1px); }
.dd > summary:hover { border-color: var(--ink); }

/* Primärfilter: etwas kräftiger als die Feineinstellungen rechts davon.
   KEINE eigene Hintergrundfarbe mehr — die stand hier nur, weil die Grundregel
   einen anderen Ton hatte. Jetzt hat sie denselben, und eine Wiederholung wäre
   eine Stelle mehr, an der die Töne auseinanderlaufen können. */
.dd-primary > summary {
  padding: 6px 26px 6px 11px; font-size: 12.5px;
}
.dd-key { font-size: 10px; letter-spacing: .08em; text-transform: uppercase; font-weight: 650; color: var(--muted); }
.dd-val { font-size: 13px; font-weight: 560; letter-spacing: -.01em; max-width: 240px; overflow: hidden; text-overflow: ellipsis; }
.dd-primary[open] > summary { border-color: var(--ink); }

/* Rot heisst hier: noch NICHT gesetzt, bitte entscheiden. Das ist die Umkehrung
   der früheren Regel («rot = von Ihnen gesetzt», siehe markState in app.js) und
   ein Entscheid des Betreibers vom Juli 2026. Damit Rot weiterhin genau EINE
   Bedeutung hat, trägt der gesetzte Zustand jetzt die Tintenfarbe statt Rot. */
/* Kein Farbsignal am leeren Zustand. Er ist der NORMALZUSTAND — die Seite zeigt
   dann alle Lösungen alphabetisch, und das ist eine gültige Ansicht. Rot wäre
   die höchste Priorität der Oberfläche und würde jeden Erstbesucher anschreien,
   um genau dann zu verstummen, wenn er etwas getan hat. Stattdessen der übliche
   Unterschied: nicht gewählt = der Wert steht als Platzhalter in gedeckter
   Schrift, gewählt = Tintenfarbe und kräftigerer Rahmen. Rot bleibt damit für
   Daten frei: Marke, Akzent, «Filter greift» am Regler, Schweizerkreuz. */
.dd-primary:not(.has-sel) .dd-val { color: var(--muted); font-weight: 500; }
.dd.has-sel > summary { border-color: var(--ink); }
.dd.has-sel .dd-val { color: var(--ink); font-weight: 560; }

/* Aufforderung als TEXT, nicht als Farbe: Text kann sagen warum, Farbe nicht.
   Verschwindet, sobald ein Profil gewählt ist. */

/* color EXPLIZIT setzen: das Sprachmenü hängt im dunklen .site-head und würde
   sonst dessen helle Schriftfarbe erben — heller Text auf weisser Fläche, also
   unlesbar. Panels liegen als eigene Fläche über der Seite und gehören darum in
   die Seitenpalette, nicht in die der Leiste. */
.dd-panel {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 60; min-width: 272px;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line-hard); border-radius: var(--r-s);
  box-shadow: var(--shadow-lg); padding: 7px; display: grid; gap: 1px;
}
/* «Ich suche»: vier Gruppen in zwei Spalten, damit das Panel nicht in die Länge wächst */
.dd-panel-wide {
  min-width: 700px; grid-template-columns: 1fr 1fr; gap: 4px 18px; padding: 10px;
  max-height: calc(100vh - 130px); overflow-y: auto;
}
.dd-group { display: grid; gap: 1px; align-content: start; }
.dd-group h4 {
  font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase; font-weight: 650;
  color: var(--muted); margin: 6px 0 4px; padding: 0 10px 5px;
  border-bottom: 1px solid var(--line);
}
.dd-group:first-child h4 { margin-top: 0; }

.dd-hint {
  grid-column: 1 / -1; font-size: 11.5px; color: var(--muted);
  padding: 2px 10px 8px; border-bottom: 1px solid var(--line); margin-bottom: 2px;
}

/* Dreistufige Kriterien: egal (nichts aktiv) / wichtig / zwingend */
.dd-want { justify-content: space-between; gap: 14px; cursor: default; }
.dd-want-label { font-size: 13px; }
.wbtns { display: inline-flex; gap: 4px; flex: none; }
.wbtn {
  border: 1px solid var(--line-hard); background: var(--surface-2); color: var(--muted);
  border-radius: 100px; padding: 2px 8px; font-size: 10.5px; cursor: pointer; white-space: nowrap;
  line-height: 1.5;
}
.wbtn:hover { border-color: var(--ink); color: var(--ink); }
.wbtn-imp.on { background: var(--ink); border-color: var(--ink); color: var(--bg); }
.wbtn-req.on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }

/* Einzelauswahl (Betriebsart) mit Radio-Punkt statt Häkchen */
.dd-radio { gap: 9px; }
.dd-radio i {
  width: 14px; height: 14px; border-radius: 50%; border: 1.5px solid var(--line-hard);
  flex: none; display: block;
}
.dd-radio.on i { border-color: var(--accent); box-shadow: inset 0 0 0 3px var(--accent); }
.dd-radio.on { color: var(--accent); font-weight: 550; }
.dd-item {
  display: flex; align-items: center; gap: 9px; font-size: 13.5px; cursor: pointer;
  padding: 7px 10px; border-radius: 4px; white-space: nowrap;
  border: 0; background: none; text-align: left; width: 100%; font: inherit; color: inherit;
}
.dd-item:hover { background: var(--surface-2); }
/* EINTRAEGE, DIE VERWEISE SIND, tragen weder Unterstreichung noch Rot. Beides kam
   von den Grundregeln für a: text-decoration-color ist rötlich getönt, und
   a:hover setzt die Schrift auf --accent. .dd-item setzt zwar color: inherit,
   aber a:hover ist damit nicht geschlagen und die Unterstreichung gar nicht
   angesprochen — also stand «Gesamtlösung für Produkte» rot unterstrichen da,
   sobald der Zeiger darauf lag. Im Sprachwechsler galt das genauso, nur fiel es
   bei vier kurzen Zeilen niemandem auf. Rot bleibt in dieser Oberfläche für
   «filtert» reserviert; ein Menüeintrag filtert nichts.
   Die Rückmeldung auf das Überfahren gibt die Fläche (.dd-item:hover). */
.dd-panel a { text-decoration: none; }
.dd-panel a:hover { color: inherit; }
/* Eintrag unter einem Gruppentitel: eingerückt, damit die Zugehörigkeit sichtbar
   ist, ohne dass der Titel wiederholt werden muss. */
.dd-item.dd-sub { padding-left: 22px; }
.dd-item input { accent-color: var(--accent); width: 15px; height: 15px; flex: none; }
.dd-pick { justify-content: space-between; gap: 16px; font-size: 13.5px; }
.dd-pick small { color: var(--muted); font-size: 11.5px; }
.dd-pick.on { background: var(--ink); color: var(--bg); }
.dd-pick.on small { color: color-mix(in srgb, var(--bg) 65%, var(--ink)); }
.dd-sep { height: 1px; background: var(--line); margin: 4px 8px; }

/* Aktiv gesetzt → rot, wie in der Tabelle */
.price-box.cuts .price-val { color: var(--accent); font-weight: 600; }

/* Sprachwechsler: nur das Kürzel, Liste beim Klick */
/* Sitzt auf der dunklen Kopfleiste, zeigt darum auf die bar-Variablen */
.dd-lang > summary {
  padding: 4px 18px 4px 6px; font-size: 11.5px; font-weight: 620; letter-spacing: .05em;
  background: none; border-color: transparent; color: var(--bar-muted);
  border-radius: var(--r-s); transition: color .14s, border-color .14s;
}
.dd-lang > summary:hover,
.dd-lang[open] > summary { color: var(--bar-ink); border-color: var(--bar-line); }
/* Kein eigenes top mehr: die Zentrierung kommt aus .dd > summary::after und
   gilt damit auch hier, wo der Umschalter niedriger ist als die Filterwähler. */
.dd-lang > summary::after {
  right: 7px; width: 5px; height: 5px; border-color: var(--bar-muted);
}
.dd-lang > summary:hover::after { border-color: var(--bar-ink); }
.dd-lang .dd-panel { left: auto; right: 0; z-index: 70; }
.dd-panel-sm { min-width: 168px; }

/* ------------------------------------------------- Kategorienmenü im Seitenkopf
   HIER UND NICHT OBEN BEI .site-nav. .dd-cats > summary und .dd > summary haben
   dieselbe Spezifität; bei Gleichstand gewinnt die spätere Regel. Oben notiert
   verlor dieser Block gegen .dd > summary in allem — weisse Fläche, 13.5px,
   andere Polsterung, heller Rahmen —, und der Wähler stach auf der dunklen
   Leiste als heller Fleck heraus. Dasselbe galt für .dd-panel-cats gegen
   .dd-panel. Wer diesen Block verschiebt, macht den Fehler zurück.

   Der Wähler sitzt auf der dunklen Leiste und zeigt darum auf die bar-Variablen,
   wie der Sprachwechsler darüber.
   ABGESETZT DURCH FORM, NICHT DURCH HELLIGKEIT: ein Versuch stellte ihn auf
   --bar-ink, um ihn als Navigation vom Sprachwechsler abzuheben. Das ist derselbe
   Ton wie die Wortmarke (#f2f3f5) — grell auf dunklem Grund, und zwei gleich
   helle Dinge nebeneinander streiten um den Rang. Jetzt dieselbe gedämpfte
   Schrift wie der Wechsler, dafür eine SICHTBARE Umrandung im Ruhezustand (der
   Wechsler hat seine nur beim Überfahren): eine Pille mit Rand liest sich als
   etwas, das sich öffnet, ohne heller zu sein als die Marke. */
.dd-cats > summary {
  display: flex; align-items: center; white-space: nowrap;
  padding: 5px 26px 5px 11px; font-size: 13px; font-weight: 560;
  background: none; border-color: var(--bar-line); color: var(--bar-muted);
  transition: color .14s, background-color .14s, border-color .14s;
}
.dd-cats > summary:hover, .dd-cats[open] > summary {
  color: var(--bar-ink);
  background: color-mix(in srgb, var(--bar-ink) 10%, transparent);
  border-color: var(--bar-muted);
}
.dd-cats > summary::after { right: 11px; border-color: var(--bar-muted); }
.dd-cats > summary:hover::after, .dd-cats[open] > summary::after { border-color: var(--bar-ink); }

/* Drei Striche für schmale Schirme, anstelle des Wortes: unter 660px ist im Kopf
   neben Marke, Panelschalter, Suchfeld und Sprachwahl kein Platz für «Alle
   Kategorien», und ein blosser Winkel ohne Wort sagt nichts. Der zweite und
   dritte Strich sind Schatten des ersten — eine Fläche, drei Linien, kein
   zusätzliches Element im Quelltext. */
.dd-cats > summary::before {
  content: ""; display: none; width: 15px; height: 1.5px; background: currentColor;
  box-shadow: 0 5px 0 currentColor, 0 10px 0 currentColor;
  /* Der untere Aussenabstand ist keine Zierde, er ZENTRIERT: align-items zentriert
     das Randfeld, und die zwei Schatten liegen 5 und 10px UNTER der Fläche. Ohne
     die 10px sässe der Strichblock um 5px zu tief. */
  margin: 0 1px 10px 1px;
}

/* MEHRSPALTENSATZ, NICHT RASTER. Bis zum 30. Juli 2026 stand jede der drei
   Gruppen in einer eigenen Spalte von 240px. Das war eine saubere Zuordnung —
   eine Spalte, ein Weg hinein —, aber die Gruppen sind ungleich gross: 3, 18 und
   19 Einträge. Unter der ersten Spalte klaffte darum eine Lücke von 16 Zeilen,
   zwei Dritteln der Feldhöhe. Ein Zwischenschritt setzte die kurze Gruppe als
   Band quer über den Kopf des Feldes; die Lücke war weg, das Feld dafür 641px
   hoch statt 585.
   Jetzt FLIESST der Inhalt: die 40 Einträge und ihre drei Titel füllen drei
   Spalten gleichmässig. Keine Lücke, und das Feld ist rund 180px flacher als
   zuvor — es passt damit auch in flache Fenster, ohne den Notausgang zu brauchen.
   DER PREIS: eine Gruppe kann unten in einer Spalte enden und oben in der
   nächsten weitergehen. Die Lesart «eine Spalte = ein Weg hinein» ist damit
   aufgegeben; die Leserichtung bleibt dieselbe (Spalte hinunter, dann die
   nächste), und die «Oder»-Kette der Titel folgt weiter der Startseite.
   break-after am Titel ist keine Zierde: ohne die Regel kann ein Titel als
   letzte Zeile einer Spalte stehen und seine Einträge beginnen in der nächsten —
   eine Überschrift ohne Inhalt darunter. */
/* DIE BREITE IST GESETZT UND NICHT GERECHNET — 3×240 Spalten + 2×20 Abstand +
   2×12 Polsterung = 784px; die 240 tragen «Gesamtlösung für Dienstleister» mit
   white-space: nowrap, den längsten Namen im Menü. Ohne die Angabe war das Feld
   rund 130px schmal und
   hatte einen waagrechten Scrollbalken, und der Grund ist eine Kette, die man
   einzeln nicht sieht:
     – .dd-panel ist absolut gesetzt, seine Breite ist also «shrink to fit»:
       min(max(Mindestinhalt, verfügbar), Wunschbreite).
     – «verfügbar» misst der Bezugsrahmen, und das ist .dd — der Wähler selbst,
       rund 130px breit.
     – .cats-cols trägt overflow-y: auto und ist damit ein Rollbereich. Ein
       Rollbereich hat einen MINDESTINHALT VON NULL: er darf beliebig schmal
       werden, sein Inhalt rollt ja.
   Damit fiel «Mindestinhalt» auf 0, «verfügbar» gewann mit 130px, und die
   760px Spalten liefen waagrecht heraus. Eine gesetzte Breite beendet die
   ganze Rechnung. */
.dd-panel-cats {
  min-width: 0; width: 784px; max-width: calc(100vw - 24px);
  padding: 12px; gap: 0;
}
.cats-cols {
  display: block; columns: 3; column-gap: 20px;
  max-height: calc(100vh - 70px); overflow-y: auto;
}
/* Die Gruppe ist im Fluss ein BLOCK und kein Raster: ein Rasterbehälter lässt
   sich über eine Spaltengrenze nicht sauber teilen, er würde als Ganzes in die
   nächste Spalte springen — und damit wären die Lücken zurück. */
.cats-cols .dd-group { display: block; }
/* Kein Titel als letzte Zeile einer Spalte, und keine Zeile durch die Mitte
   geschnitten. */
.cats-cols .dd-group h4 { break-after: avoid; }
.cats-cols .cats-i { break-inside: avoid; }
/* Erste Zeile ist die Startseite: sie führt aus dem Menü heraus und steht darum
   ÜBER den Spalten und durch eine Linie abgesetzt. */
.cats-start {
  font-weight: 620; border-bottom: 1px solid var(--line);
  border-radius: 4px 4px 0 0; margin-bottom: 8px;
}
/* KNAPPE ZEILENHOEHE, gerechnet: 12px Schrift mal 1.35 sind 16px, plus zweimal
   4px Polsterung und 1px Abstand ergibt 25px je Zeile. Bei 19 Zeilen in der
   längsten Spalte bleibt das Feld mit Kopfzeilen und Rand unter 600px und passt
   damit in ein Fenster von 660px Höhe — ohne Scrollbalken. */
.cats-i { padding: 4px 9px; font-size: 12px; line-height: 1.35; gap: 10px; }
/* Sicherheitsnetz: kein Name kann aus seiner Spalte laufen, auch wenn eine
   Kategorie einmal einen längeren bekommt. min-width: 0 ist dafür nötig — ein
   Flex-Kind schrumpft sonst nicht unter seine Inhaltsbreite. */
.cats-i span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.cats-i small { font-variant-numeric: tabular-nums; flex: none; }
/* Die laufende Kategorie: markiert wie die gewählte Sprache im Wechsler — das
   Menü sagt damit auch, wo man steht. */
.cats-i.on { font-weight: 620; }

@media (max-width: 980px) {
  /* Als Blatt statt als Feld: drei Spalten von 240px plus Abstände und Rand
     brauchen 784px, und links davon steht die Wortmarke. Darunter ragte das Feld
     rechts aus dem Fenster. */
  .dd-panel-cats {
    position: fixed; left: 8px; right: 8px; top: calc(var(--head-h) + 6px);
    /* width: auto, sonst schlägt die gesetzte Breite von oben die zwei Ränder
       und das Blatt ragte rechts hinaus. */
    width: auto; max-width: none;
    max-height: calc(100vh - var(--head-h) - 22px); overflow-y: auto;
  }
  .cats-cols { columns: 2; max-height: none; }
}
@media (max-width: 660px) {
  /* Kein Winkel NEBEN den drei Strichen: zwei Zeichen für dasselbe. */
  .dd-cats > summary::before { display: block; }
  .dd-cats > summary::after { display: none; }
  .dd-cats > summary { padding: 5px 10px; }
  .cats-cols { columns: 1; }
}

.price-box { display: flex; align-items: center; gap: 10px; }
/* Im schmalen Panel UEBEREINANDER und der Regler ueber die ganze Breite: mit
   festen 130px nebeneinander lief die rote Spur ueber den rechten Rand hinaus. */
.fpanel .price-box { display: grid; gap: 4px; }
.fpanel .price-box label { white-space: normal; font-size: 11.5px; }
.fpanel #priceRange { width: 100%; }
.fpanel .check { font-size: 11.5px; }
.price-box label { font-size: 12.5px; color: var(--muted); white-space: nowrap; }
.price-val { color: var(--ink); font-variant-numeric: tabular-nums; }
#priceRange { width: 130px; accent-color: var(--accent); }

.check { font-size: 12.5px; color: var(--muted); display: flex; align-items: center; gap: 7px; cursor: pointer; white-space: nowrap; }
.check input { accent-color: var(--accent); width: 14px; height: 14px; }

/* «nur Unterschiede» mit Info-Zeichen. Der Hinweis erklärt eine Option, deren
   Name allein nicht sagt, was sie tut — sie blendet Zeilen aus, in denen ALLE
   Einträge dasselbe sagen. Ohne Erklärung wirkt das wie ein Fehler. */
.check-wrap { display: flex; align-items: center; gap: 5px; flex: none; }
.info {
  position: relative; flex: none; width: 15px; height: 15px; padding: 0;
  display: grid; place-items: center; cursor: help;
  background: none; border: 1px solid var(--line-hard); border-radius: 999px;
  color: var(--muted); font-size: 10px; font-weight: 700; font-style: italic;
  font-family: inherit; line-height: 1;
}
.info:hover, .info:focus-visible { color: var(--ink); border-color: var(--muted); }

/* Der Hinweis erscheint OHNE Verzögerung — eine Wartezeit lässt den Leser
   zweifeln, ob das Zeichen überhaupt etwas tut. Bewusst dunkel in beiden
   Paletten: ein Tooltip soll sich vom Untergrund abheben, nicht einfügen. */
.info-tip {
  position: absolute; top: calc(100% + 9px); left: 50%; transform: translateX(-50%);
  z-index: 60; width: max-content; max-width: 260px;
  padding: 9px 11px; border-radius: var(--r-s);
  background: #111; color: #fff;
  font-size: 12.5px; font-weight: 400; font-style: normal; line-height: 1.5;
  text-align: left; white-space: normal;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .28);
  opacity: 0; visibility: hidden; pointer-events: none;
}
/* Zeiger zum Zeichen hoch, aus demselben Schwarz wie die Fläche. */
.info-tip::before {
  content: ''; position: absolute; bottom: 100%; left: 50%; margin-left: -5px;
  border: 5px solid transparent; border-bottom-color: #111;
}
/* :focus UND :focus-visible. Auf einem Berührungsbildschirm gibt es kein Hover,
   und :focus-visible behalten die Browser in der Regel der Tastatur vor — ein
   Fingertipp auf das «i» zeigte damit NICHTS. Der Hinweis war auf dem Handy also
   nicht vorhanden, obwohl das Zeichen da stand. Mit :focus oeffnet ihn ein Tipp,
   und ein Tipp daneben schliesst ihn wieder (der Schalter verliert den Fokus). */
.info:hover .info-tip,
.info:focus .info-tip,
.info:focus-visible .info-tip { opacity: 1; visibility: visible; }
/* Auf dem Handy hat «cursor: help» keine Bedeutung, und der Hinweis darf die
   Bildschirmbreite nicht ueberschreiten. */
@media (max-width: 660px) {
  .info-tip { max-width: calc(100vw - 40px); }
  .info-tip-wide { max-width: calc(100vw - 40px); font-size: 12.5px; }
  .tip-tab { font-size: 11px; gap: 2px 8px; }
}
/* Breitere Fassung fuer einen Hinweis, der einen Gedanken erklaert und nicht
   nur einen Schalter. 260px zwaengen fuenf Saetze in einen Turm. */
.info-tip-wide { max-width: 480px; font-size: 13px; line-height: 1.5; padding: 12px 14px; }

/* Struktur der Erklärinhalte — benannte Abschnitte, Feldlisten, Sammelübersicht.
   Sie erscheinen an ZWEI Orten: im schwarzen Hinweis an einer Zeile und im hellen
   Schubfach am Abschnittstitel. Die FORM ist dieselbe, die Farben kommen aus dem
   Umfeld.

   Vorher standen die Farben fest in diesen Regeln — gewählt für den schwarzen
   Hinweis. Im Schubfach ergab das hellgrauen Text auf Weiss. Farben, die an der
   Form kleben, brechen beim ersten Ortswechsel. */
.info-tip {
  --tipfg: #fff; --tipfg2: #d7dade; --tipfg3: #a9adb3; --tipline: #333;
}
#drawerBody {
  --tipfg: var(--ink); --tipfg2: var(--ink-soft); --tipfg3: var(--muted); --tipline: var(--line);
}
/* Überschrift und Absatz */
.tip-h { display: block; font-weight: 700; font-size: 12.5px; letter-spacing: .01em; color: var(--tipfg); }
.tip-p { display: block; margin: 2px 0 10px; color: var(--tipfg2); }
.tip-p:last-child { margin-bottom: 0; }
/* Feldliste: Beschriftung links, Wert rechts. Wer die MWST-Methode sucht, findet
   sie an einer festen Stelle statt irgendwo in einer Aufzählung. */
.tip-dl { display: grid; grid-template-columns: auto 1fr; gap: 3px 14px; align-items: baseline; }
.tip-k { color: var(--tipfg3); font-size: 11.5px; }
.tip-v { color: var(--tipfg); font-variant-numeric: tabular-nums; }
/* Sammelübersicht: eine Zeile je Fall, eine Spalte je Kennwert. Erst
   untereinander sind die Annahmen vergleichbar — in fünf einzelnen Hinweisen
   müsste man sie im Kopf behalten. */
.tip-tab {
  display: grid; grid-template-columns: auto repeat(calc(var(--tipcols) - 1), auto);
  gap: 3px 12px; margin-top: 4px; font-size: 11.5px; white-space: nowrap;
}
.tip-th { color: var(--tipfg3); font-weight: 600; border-bottom: 1px solid var(--tipline); padding-bottom: 3px; }
.tip-rh { color: var(--tipfg); font-weight: 600; }
.tip-td { color: var(--tipfg2); font-variant-numeric: tabular-nums; }
/* Im Schubfach ist Platz: dort darf die Übersicht grösser und luftiger stehen. */
#drawerBody .tip-tab { font-size: 13px; gap: 6px 18px; margin-top: 10px; }
#drawerBody .tip-p { font-size: 14px; line-height: 1.55; }
#drawerBody .tip-h { font-size: 13px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
/* Das Zeichen am Gruppentitel: die Gruppenzeile setzt Grossbuchstaben und
   Sperrung, der Hinweis darin nicht -- das erledigt .info-tip schon. Der
   Abstand haelt es vom Titel weg, ohne ihn zu verschieben. */
/* Das Zeichen am Gruppentitel ist ETWAS deutlicher als das in der Bedienleiste,
   aber keine Farbfläche: es blieb sonst als roter Punkt in der Zeile stehen und
   zog mehr Aufmerksamkeit als der Titel, den es erklärt. Also der gewohnte Ring
   — nur mit kräftigerem Rand und dunklerer Schrift, damit er in der grauen
   Gruppenzeile nicht untergeht.
   Die Zeile setzt Grossbuchstaben und Sperrung; beides muss zurückgesetzt
   werden, sonst wird aus dem «i» ein gesperrtes «I». */
/* Erklärzeichen IN DER TABELLE. Gleiche GRÖSSE, verschiedenes GEWICHT — und das
   mit Absicht:

   Am Abschnittstitel ist es gefüllt und dunkel. Dahinter steht die Idee der
   Kategorie: dass jede Zahl an einem festgelegten Betrieb gemessen ist. Wer das
   nicht liest, versteht die ganze Tabelle falsch, also darf das Zeichen auffallen.

   An einer Fallzeile ist es der leise Ring. Dahinter stehen die Annahmen dieses
   einen Falls — nützlich, aber ein Detail auf Abruf. Fünf gefüllte Zeichen
   untereinander zogen mehr Aufmerksamkeit als die Preise daneben, und die sind
   der Inhalt.

   Nicht --accent: Rot ist auf dieser Seite dem Zustand «gesetzt» vorbehalten. Beim
   Überfahren wechseln beide darauf — dann IST das Zeichen angefasst. */
tr.grouprow .info, td.rowlab .info {
  display: inline-grid; width: 15px; height: 15px;
  font-size: 9.5px; letter-spacing: 0; text-transform: none;
}
tr.grouprow .info {
  margin-left: 8px; vertical-align: middle; font-weight: 700;
  background: var(--ink-soft); border-color: var(--ink-soft); color: #fff;
}
td.rowlab .info {
  margin-left: 6px; vertical-align: 1px; font-weight: 700;
  background: none; border-color: var(--line-hard); color: var(--muted);
}
tr.grouprow .info:hover, tr.grouprow .info:focus-visible {
  background: var(--accent); border-color: var(--accent); color: #fff;
}
td.rowlab .info:hover, td.rowlab .info:focus-visible {
  border-color: var(--accent); color: var(--accent);
}

/* Der Hinweis hängt LINKS am Zeichen, nicht mittig darunter. Mittig zentriert
   ragte die halbe Breite über den linken Rand der Tabelle hinaus und wurde vom
   Rollbereich abgeschnitten — die Zeichen stehen am Anfang der Zeile, da ist
   links kein Platz. Der Zeiger sitzt dafür über dem Zeichen. */
tr.grouprow .info-tip, td.rowlab .info-tip { left: 0; transform: none; }
tr.grouprow .info-tip::before, td.rowlab .info-tip::before { left: 7px; margin-left: 0; }

/* STAPELREIHENFOLGE: die klebenden Zellen bilden eigene Stapelkontexte, und
   darin nuetzt dem Hinweis sein z-index nichts — er kann seinen Kontext nicht
   verlassen. Die Beschriftungsspalte liegt auf 2, die Gruppenzeilen auf 4, der
   Tabellenkopf auf 6 und die Ecke auf 8. Ein Hinweis aus einer Zeile wurde
   deshalb von den Gruppenzeilen und vom Kopf ueberdeckt — er sah abgeschnitten
   aus, obwohl er vollstaendig gezeichnet war.
   Abhilfe: die Zelle selbst nach vorn holen, solange ihr Hinweis offen ist.
   :focus-within deckt die Tastaturbedienung mit ab — ohne sie waere der Hinweis
   nur mit der Maus lesbar. */
td.rowlab:hover, td.rowlab:focus-within,
tr.grouprow td:hover, tr.grouprow td:focus-within { z-index: 9; }

/* KEIN automatischer Rand: die Anzahl steht direkt hinter dem letzten Filter
   und schliesst die Gruppe ab. Den Abstand zum rechten Rand erzeugt .chside. */
.result-count {
  font-size: 12.5px; color: var(--muted); text-align: left;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
.result-count b { color: var(--ink); font-weight: 640; font-variant-numeric: tabular-nums; }

/* Schalter in der Anzahlzeile: «alle zeigen» und «Auswahl aufheben». Als Text
   und nicht als Knopf — sie erscheinen nur, wenn eine Auswahl besteht, und
   sollen die Zeile nicht in eine zweite Bedienleiste verwandeln. */
.rc-btn {
  border: 0; background: none; padding: 0 1px; cursor: pointer;
  font: inherit; color: var(--accent);
  text-decoration: underline; text-underline-offset: 3px;
}
.rc-btn:hover { color: var(--ink); }
/* EINGESCHALTET: die zwei Stufenschalter («nur diese», «nur Unterschiede») sind
   Zustände und keine einmaligen Handlungen — man muss ihnen ansehen, ob sie
   gerade gelten. Gefüllt und ohne Unterstreichung: die Unterstreichung heisst
   auf dieser Zeile «hier lässt sich etwas auslösen», der gefüllte Grund «es ist
   ausgelöst». Rot ist hier richtig und keine dritte Bedeutung — es heisst auf
   dieser Seite «von Ihnen gewählt», und genau das ist es. */
.rc-btn.on {
  background: var(--accent); color: var(--accent-ink);
  text-decoration: none; border-radius: 3px; padding: 1px 6px;
}
.rc-btn.on:hover { color: var(--accent-ink); opacity: .88; }
.rc-note { color: var(--muted); }

.btn {
  background: var(--ink); color: var(--bg); border: 1px solid var(--ink);
  padding: 8px 15px; border-radius: var(--r-s); cursor: pointer; font-size: 14px;
  font-weight: 550; text-decoration: none; display: inline-block;
}
.btn:hover { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.btn-ghost { background: transparent; color: var(--ink); border-color: var(--line-hard); }
.btn-ghost:hover { background: transparent; color: var(--accent); border-color: var(--accent); }

/* ---------------------------------------------------------------- table */
/* Der Vergleich nutzt die ganze Bildschirmbreite: randlos, ohne Rahmenradius.
   Der Rest der Seite bleibt in der lesbaren Spaltenbreite. */
.view { padding: 0 0 8px; }
.wrap-full { max-width: none; padding: 0; }
.wrap-pad { max-width: none; padding: 0 20px; }

/* Keine eigene Scrollleiste mehr — gescrollt wird der Bereich .scroll.
   width: max-content, damit die Rahmenlinien über die ganze Tabellenbreite laufen. */
.table-scroll {
  background: var(--surface); width: max-content; min-width: 100%;
  border-top: 1px solid var(--line-hard); border-bottom: 1px solid var(--line-hard);
}
table.cmp { border-collapse: separate; border-spacing: 0; width: 100%; font-size: 13.5px; }
table.cmp th, table.cmp td { text-align: left; border-bottom: 1px solid var(--line); }
table.cmp td { padding: 8px 14px; white-space: nowrap; }

/* Kopfzelle je Programm */
table.cmp thead th {
  position: sticky; top: 0; z-index: 6; background: var(--surface-2);
  border-bottom: 1px solid var(--line-hard); vertical-align: top;
  padding: 12px 14px 11px; min-width: 172px;
}
/* DIE AUSWAHL BRAUCHT «table.cmp thead th.corner» UND NICHT NUR «th.corner».
   Mit dem kurzen Namen gewann die Regel darueber und die Eckzelle bekam z-index
   6 statt 8 — gleichauf mit allen anderen Kopfzellen. Bei gleichem Rang
   entscheidet die Reihenfolge im Dokument, und die Eckzelle steht ZUERST: jede
   Anbieterspalte, die beim Seitwaertsscrollen unter sie faehrt, wurde ueber sie
   gemalt. Gemessen stand an der Stelle, wo «KRITERIUM» steht, der Name
   «Banana Buchhaltung». Auf breiten Schirmen faellt es kaum auf, auf 390px ist
   die Eckzelle ein Drittel des Bildes. */
table.cmp thead th.corner {
  position: sticky; left: 0; top: 0; z-index: 8; width: 190px; min-width: 190px;
  background: var(--surface-2); border-right: 1px solid var(--line-hard);
  font-size: 11px; letter-spacing: .09em; text-transform: uppercase; color: var(--muted); font-weight: 650;
}
.pth-row { display: flex; align-items: baseline; gap: 6px; }
/* EINE FLAECHE FUER EINE HANDLUNG: Kästchen und Name liegen seit dem 1. August
   2026 in EINEM Schalter, und der merkt vor. Der Name war bis dahin ein Verweis
   auf die Produktseite — diesen Weg trägt jetzt «Details» darunter, als echter
   <a href>, damit die 123 Produktseiten aus den Kategorieseiten erreichbar
   bleiben. Genau das war schon einmal kaputt und wurde im Juli 2026 behoben.
   Der Schalter sieht aus wie Text: keine Fläche, kein Rahmen. Beim Überfahren
   färbt sich das Kästchen — der Hinweis kommt, wenn er gebraucht wird. */
.pth-sel {
  display: flex; align-items: baseline; gap: 6px; flex: 1;
  border: 0; background: none; padding: 0; margin: 0;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.pth-name { font-size: 15.5px; font-weight: 640; letter-spacing: -.02em; text-decoration: none; }
.pth-sel:hover .pth-pick { border-color: var(--accent); }
.pth-sel:hover .pth-name { color: var(--accent); }

/* Auswahlzeichen je Spalte. Ein leeres QUADRAT, gefüllt sobald vorgemerkt —
   kein Häkchen darin, weil ein Häkchen in dieser Tabelle schon «Merkmal
   vorhanden» bedeutet und die zweite Bedeutung verwirren würde.

   RUND WAR ES BIS ZUM 1. AUGUST 2026, und das war das falsche Signal: rund
   heisst in jeder Oberfläche «eines von mehreren», eckig «mehrere möglich».
   Vormerken ist ausdrücklich eine Mehrfachwahl, und wer sich nach der Form
   richtet, erwartete beim zweiten Klick, dass die erste Wahl verfällt.
   Die alte Begründung für den Kreis stimmte, traf aber daneben: die Kollision
   liegt beim HÄKCHEN, nicht beim Quadrat. Ohne Häkchen gibt es sie nicht.
   Nebenbei dieselbe Form wie die Kriterienkästchen links — zwei Achsen, ein
   Mechanismus, eine Form.

   Zurückhaltend im Ruhezustand: die Tabelle hat bis zu dreissig Spalten, und
   dreissig kräftige Zeichen wären eine Aufforderung statt eines Angebots. */
.pth-pick {
  flex: none; align-self: center; width: 13px; height: 13px; padding: 0;
  border: 1.5px solid var(--line-hard); border-radius: 3px;
  background: none; cursor: pointer; transition: background-color .12s, border-color .12s;
}
.pth-sel.on .pth-pick { border-color: var(--accent); background: var(--accent); }

/* Die vorgemerkte Spalte trägt eine Kante in derselben Farbe. Sie ist die
   Bestätigung, dass der Klick die SPALTE meint und nicht nur das Zeichen. */
/* DREI PIXEL, NICHT ZWEI — und an beiden Achsen gleich. Zwei Pixel trugen die
   Aussage, verschwanden aber neben den Rahmenlinien der Tabelle, die selbst
   einen Pixel stark sind: ein Strich, der nur um die Haelfte kraeftiger ist als
   jede Trennlinie daneben, liest sich als Trennlinie. Drei Pixel setzen ihn
   ausser Zweifel und bleiben eine Kante — im Unterschied zur Flaeche, die den
   Werten in der Tabelle die Aufmerksamkeit nimmt.

   EIN PIXEL LUECKE ZWISCHEN BENACHBARTEN STRICHEN, genau wie links. Dort
   entsteht sie von selbst: jede Zeile traegt ihren eigenen Strich, und dazwischen
   liegt die Trennlinie der Zeile. Oben stossen zwei vorgemerkte Spalten
   unmittelbar aneinander, und die zwei Striche verschmolzen zu einem einzigen
   langen — zwei Spalten sahen aus wie eine.
   Als Farbverlauf und nicht als inset-Schatten: ein Schatten laesst sich nicht
   waagrecht einruecken, ein Hintergrundbild schon. Die Luecke sitzt RECHTS und
   nicht auf beiden Seiten — so ist sie genau einen Pixel breit wie links, statt
   zwei, und der aeusserste Strich beginnt buendig mit seiner Spalte. */
/* DER STRICH IST EIN PSEUDOELEMENT UND KEINE HINTERGRUNDFARBE — und das ist
   keine Geschmacksfrage, sondern die Lehre aus zwei Fehlern am 1. August 2026.
   Erst war er ein Farbverlauf auf dem <th>: «table.cmp thead th» setzt
   background als KURZFORM, und eine Kurzform setzt background-image auf none
   zurueck; mit (0,1,2) gegen (0,1,0) verschwand der Strich ganz. Nach dem
   Anheben der Spezifitaet verschwand der ZEILEN-Strich beim Ueberfahren, weil
   «table.cmp tbody tr:hover td:not(.grp)» ebenfalls background als Kurzform
   setzt — dieselbe Falle, zweite Tuer.
   Ein Pseudoelement hat einen eigenen Hintergrund. Keine Regel auf der Zelle
   kann ihn loeschen, egal mit welcher Spezifitaet. Damit ist die Falle nicht
   umgangen, sondern zugemauert.
   Die Zelle traegt position: sticky und ist damit schon Bezugsrahmen; ein
   zusaetzliches position: relative braucht es nicht. */
table.cmp thead th.pth-picked::before {
  content: ''; position: absolute; left: 0; right: 2px; top: 0; height: 3px;
  background: var(--accent);
}
/* KEINE FLAECHE, WEDER HIER NOCH BEI DEN ZEILEN. Ausprobiert am 1. August 2026
   und verworfen: die Spalte war mit 6% Rot hinterlegt, wie es die Zeilen der
   Gruppe «Ihre Auswahl» damals waren. Eine Fläche über dreissig Zeilen nimmt
   den Werten darin die Aufmerksamkeit, und genau die sind der Zweck der
   Tabelle. Geblieben ist die LINIE an der Beschriftungskante — oben an der
   Spalte, links an der Zeile. Eine Handlung, ein Zeichen, zwei Achsen. */
.pth-short { font-size: 11.5px; color: var(--muted); margin-top: 2px; white-space: normal; max-width: 200px; line-height: 1.35; }
.pth-fit { display: flex; align-items: center; gap: 7px; margin-top: 8px; font-size: 11px; color: var(--muted); }
/* «Details» als stiller Verweis IN der Beschreibungszeile, nicht als umrandeter
   Knopf darunter. Als Knopf war er das lauteste Element im Kopf — lauter als
   der Produktname, den er begleitete —, und er kostete den klebenden Kopf eine
   dritte gestapelte Zeile. Zusammen mit dem entfallenen × sinkt die Kopfzelle
   von 116 auf rund 70px, also von 15 auf 9 Prozent des Sichtfensters. */
/* IMMER EINE EIGENE ZEILE. Als reines Inline-Element rutschte «Details» hinter
   die Beschreibung, sobald die kurz genug war — und stand bei langen
   Beschreibungen darunter. Zwei Kopfzellen nebeneinander sahen damit
   verschieden gebaut aus, obwohl sie dasselbe enthalten. display: block bindet
   den Verweis an seine eigene Zeile, ohne dass er dafuer aus dem
   Beschreibungsblock heraus muesste — der Kopf bleibt zweiteilig. */
.pth-detail {
  display: block; margin-top: 2px;
  border: 0; background: none; padding: 0; cursor: pointer;
  font: inherit; font-size: 11.5px; color: var(--muted);
  text-decoration: underline; text-underline-offset: 2px;
  text-decoration-color: var(--line-hard);
}
.pth-detail:hover { color: var(--accent); text-decoration-color: var(--accent); }
.pth-hits { font-size: 11px; color: var(--muted); margin-top: 5px; }
.pth-hits.full { color: var(--good); font-weight: 600; }

/* Zeilen */
td.rowlab {
  position: sticky; left: 0; z-index: 2; background: var(--surface);
  border-right: 1px solid var(--line-hard); color: var(--ink-soft); font-weight: 500;
  width: 190px; min-width: 190px; white-space: normal;
}
/* Gruppentitel bleiben in BEIDEN Richtungen sichtbar: die Zeile klebt senkrecht
   unter der Kopfzeile (--thead-h wird in app.js gemessen), die Beschriftung
   klebt waagrecht am linken Rand. Die Zelle selbst kann nicht waagrecht kleben,
   weil sie über die ganze Tabellenbreite geht — darum der innere Span. */
tr.grouprow td {
  background: var(--surface-2); font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  font-weight: 650; color: var(--muted); padding: 9px 0 6px;
  border-top: 1px solid var(--line-hard); border-bottom: 1px solid var(--line-hard);
  position: sticky; top: var(--thead-h, 108px); z-index: 4;
}
/* Titel und Erklaerzeichen kleben GEMEINSAM: die Huelle klebt, nicht die
   Beschriftung allein. Vorher klebte nur .grplab, und das Zeichen lief beim
   waagrechten Scrollen aus dem Bild, waehrend der Titel stehen blieb. */
tr.grouprow .grphead {
  position: sticky; left: 0; display: inline-flex; align-items: center;
  padding: 0 14px; max-width: 100%;
}
tr.grouprow .grplab { display: inline-block; }
/* Erklaerlink am Abschnittstitel. Die Gruppenzeile ist gesperrt und in
   Grossbuchstaben gesetzt — der Link setzt beides zurueck. Kleinschreibung neben
   Grossbuchstaben trennt die zwei Rollen deutlicher als jede Farbe: der Titel
   benennt, der Link handelt.
   NICHT rot: Rot heisst auf dieser Seite «gesetzt», und ein dauerhaft rotes
   Element in jeder Gruppenzeile hiesse, dort sei etwas gewaehlt. */
tr.grouprow .grplink {
  margin-left: 10px; padding: 0; border: 0; background: none;
  font: inherit; font-weight: 500; font-size: 11px;
  text-transform: none; letter-spacing: 0;
  color: var(--ink-soft); cursor: pointer;
  text-decoration: underline; text-decoration-color: var(--line-hard);
  text-underline-offset: 2px; text-decoration-thickness: 1px;
}
/* Mittelpunkt als Trenner — dieselbe Fuegung wie ueberall auf dieser Seite.
   Nicht unterstrichen: er gehoert nicht zum Link, er trennt ihn ab. */
tr.grouprow .grplink::before {
  content: '·'; display: inline-block; margin-right: 10px;
  color: var(--line-hard); text-decoration: none;
}
tr.grouprow .grplink:hover, tr.grouprow .grplink:focus-visible {
  color: var(--accent); text-decoration-color: var(--accent);
}
/* Auf schmalen Geraeten rutscht der Link unter den Titel, statt die klebende
   Huelle ueber die halbe Bildschirmbreite zu ziehen. */
tr.grouprow .grphead { flex-wrap: wrap; row-gap: 2px; }
@media (max-width: 660px) {
  /* flex-basis: 100% erzwingt den Umbruch. Ohne ihn blieb er aus: die Huelle
     ist eine Zelle mit colspan ueber die ganze Tabelle und damit tausende
     Pixel breit — es gab nie einen Grund umzubrechen, und «VERGLEICHSPREISE»
     und «So vergleichen wir» klebten ohne Trenner aneinander, weil hier
     zugleich der Mittelpunkt entfaellt. */
  tr.grouprow .grplink { margin-left: 0; flex: 1 0 100%; text-align: left; }
  tr.grouprow .grplink::before { content: none; margin: 0; }
}
/* Hinweiszeile eines Abschnitts: der gezählte Befund unter dem Titel
   «Vergleichspreise». Sie steht IN der Tabelle, weil über der Tabelle höchstens
   ein Streifen stehen darf — und weil sie hier bei den Zeilen steht, über die sie
   etwas sagt.
   NICHT klebend und NICHT in Grossbuchstaben wie die Gruppenzeile: es ist ein
   Satz zum Lesen, kein Titel. Der innere Span klebt waagrecht, damit er beim
   Scrollen einer breiten Tabelle nicht aus dem Bild läuft — dasselbe Verfahren
   wie beim Abschnittstitel. */
tr.noterow td {
  background: var(--surface-2); padding: 0 0 9px;
  border-bottom: 1px solid var(--line-hard);
}
tr.noterow .notein {
  position: sticky; left: 0; display: block; padding: 0 14px;
  font-size: 12.5px; line-height: 1.45; font-weight: 400;
  letter-spacing: 0; text-transform: none; color: var(--ink-soft);
  max-width: 74ch; white-space: normal;
}

/* Persönliche Kriterien: eigene Gruppe am Anfang der Tabelle */
/* NUR DIE BESCHRIFTUNG ROT, NICHT DIE LINIE. Die Trennlinie der Gruppe war
   ebenfalls rot eingefaerbt und lief damit ueber die ganze Tabellenbreite —
   das groesste rote Element der Seite fuer die leiseste Aussage. Seit die
   gewaehlten Zeilen ihren Strich an der Beschriftungskante tragen, sagt Rot
   hier dreimal dasselbe: Linie, Ueberschrift, Strich. Die Linie ist die
   entbehrlichste davon; sie bleibt als normale Trennlinie stehen, wie bei
   jeder anderen Gruppe auch. */
tr.grouprow-own td { color: var(--accent); }
/* DER ROTE STRICH AN DER BESCHRIFTUNGSKANTE, das Gegenstück zu .pth-picked
   oben an der Spalte. Bis zum 1. August 2026 war die ganze Zeile mit 6% Rot
   hinterlegt; die Fläche ist auf Wunsch des Betreibers gefallen, der Strich
   sagt dasselbe und lässt den Werten den Vordergrund.
   Als inset-Schatten und nicht als border-left: die Beschriftungsspalte klebt
   (position: sticky) und trägt bereits eine Rahmenlinie rechts — ein zweiter
   Rahmen links verschöbe ihre Breite und damit die ganze Tabelle um 2px. */
/* OHNE eigenes Gewicht. Die Zeile ist bereits dreifach gekennzeichnet — roter
   Strich links, Gruppe «Ihre Auswahl», Stellung oben —, und die
   Beschriftungsspalte traegt seit dem 1. August 2026 genau EIN Gewicht. Eine
   Spalte mit einem Gewicht liest sich als Spalte, eine mit dreien als Liste mit
   Ausnahmen. */
tr.ownrow td.rowlab {
  color: var(--ink);
}
tr.ownrow td.rowlab::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 1px; width: 3px;
  background: var(--accent);
}
/* KLEINER UND UMRANDET STATT GEFUELLT, seit dem 1. August 2026. Als gefuellte
   Pillen — dunkel fuer «wichtig», rot fuer «zwingend» — sprachen sie lauter als
   der Kriterienname, den sie begleiten, und der ist die Hauptsache. Dasselbe
   Argument wie beim managed-Etikett auf der Startseite.
   Die Farbe bleibt und traegt weiter die Unterscheidung: rot ist «zwingend»,
   grau «wichtig». Sie steht jetzt in Schrift und Rand statt in der Flaeche.
   Die Zeile ist ohnehin schon dreifach gekennzeichnet — roter Strich links,
   Gruppe «Ihre Auswahl», Stellung oben. Das Etikett muss nur noch sagen,
   WELCHE der zwei Stufen gilt. */
.lvl {
  display: inline-block; margin-left: 8px; font-size: 9px; font-weight: 650;
  letter-spacing: .06em; text-transform: uppercase; padding: 0 5px; border-radius: 100px;
  border: 1.5px solid; background: none; vertical-align: 1px;
}
/* lvl-case = die Zeile, die den gewaehlten Fall selbst fuehrt (Preisprofile).
   Dasselbe Kennzeichen wie die uebrigen Auswahlarten, damit jede Tabelle
   gleich aussieht -- ein eigener Farbweg dafuer ist entfallen. */
.lvl-imp, .lvl-profile, .lvl-case { color: var(--ink-soft); border-color: var(--line-hard); }
.lvl-req { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); }

/* Generell entscheidungsrelevante Zeilen: nur die Beschriftung kräftiger,
   KEIN roter Hintergrund — Rot bleibt den eigenen Kriterien vorbehalten. */
/* «Entscheidungsrelevante» Zeile (key: true im Datensatz). Sie stand auf 620,
   die uebrigen auf 500 — zwei Sorten Beschriftung, deren Unterschied nirgends
   erklaert war. Geblieben ist die dunklere Tinte, die Strichstaerke ist weg:
   eine Spalte mit einem Gewicht liest sich als Spalte, eine mit zweien als
   Liste mit Ausnahmen. */
tr.keyrow td.rowlab { color: var(--ink); }
table.cmp tbody tr:hover td:not(.grp) { background: color-mix(in srgb, var(--accent) 9%, var(--surface)); }
tr.linkrow td { padding-top: 10px; padding-bottom: 12px; font-size: 12.5px; }
tr.linkrow a { color: var(--muted); }
tr.linkrow a:hover { color: var(--accent); }

/* --------------------------------------------------- Preisbalken (Magnitude)
   Eine Messgrösse, eine Farbe: neutraler Ton, kein Signalrot. Die Zahl steht
   immer daneben, der Balken trägt die Information nie allein.
   Dunkelmodus ist ein eigener, gewählter Ton — keine automatische Umkehrung. */
:root { --bar: color-mix(in srgb, var(--ink) 58%, var(--surface)); }
html[data-theme="dark"] { --bar: color-mix(in srgb, var(--ink) 62%, var(--surface)); }

tr.pricerow td { padding-top: 7px; padding-bottom: 8px; }
.pcell { display: flex; flex-direction: column; gap: 5px; }
/* EIN GEWICHT FUER ALLE WERTE — siehe die Begruendung bei table.cmp td.num.
   Hier stand 450, damit der Listenpreis leichter wog als die Vergleichspreise
   darunter. Die Absicht war richtig, das Mittel falsch: eine Rangfolge in
   Strichstaerken ist eine Wertung, die der Leser nicht nachpruefen kann. */
.pval { font-variant-numeric: tabular-nums; white-space: nowrap; }
.pval-na { color: var(--muted); font-weight: 500; }
.ptrack {
  display: block; position: relative; height: 4px; border-radius: 2px; width: 118px; max-width: 100%;
  background: color-mix(in srgb, var(--line-hard) 55%, var(--surface));
}
/* Korridor: Balken von Einstiegs- bis Ausbaupreis, Enden gerundet. Hiess bis
   Juli 2026 .seg und erbte dadurch versehentlich Rahmen und overflow von der
   generischen .seg-Regel des Ansichtsumschalters — bei 4px Höhe sichtbar. */
.ptrack .pbar {
  position: absolute; top: 0; height: 100%; min-width: 4px;
  border-radius: 2px; background: var(--bar);
}
/* .pval-span trug 560, um die Spanne schwerer zu setzen als den Listenpreis.
   Entfallen mit der Vereinheitlichung der Gewichte: was die Spanne von den
   Fallzeilen unterscheidet, sagt ihre Beschriftung und ihr Balken, nicht ihre
   Strichstaerke. */
/* Teilspanne: nicht alle Fälle erhoben. Derselbe Balken, nur blasser — KEIN
   einseitiges Auslaufen wie beim offenen Ende, denn die fehlenden Fälle könnten
   höher oder tiefer liegen. Eine Richtung zu zeigen wäre geraten. */
.ptrack .pbar-teil { opacity: .45; }
/* Offenes Ende: Hersteller nennt nur einen «ab»-Preis */
.ptrack .pbar-open {
  background: linear-gradient(90deg, var(--bar), color-mix(in srgb, var(--bar) 15%, transparent));
  width: 26px !important;
}
.scalenote { display: block; font-size: 10.5px; font-weight: 400; color: var(--muted); margin-top: 2px; }
/* Zeile «Preisangabe»: woher der Preis kommt. Bewusst leicht und klein — sie
   raeumt die Aufmerksamkeit den Vergleichspreisen darunter ein und enthaelt
   keine Zahl, die verankern koennte. Gestrichelte Unterstreichung, wo ein
   Hinweis mit den Tarifstufen daran haengt. */
.pinfo { font-size: 12.5px; font-weight: 450; }
.pinfo-na { color: var(--muted); font-weight: 400; }
/* Annahmen unter einer Zeilenbeschriftung (Preisprofile). Bewusst NICHT
   umbruchfrei und nicht schmaler als die Spalte: die vier Angaben stehen in
   fester Reihenfolge, damit sie sich von Zeile zu Zeile untereinander lesen
   lassen. Etwas heller und kleiner als die Beschriftung, damit der Name die
   Zeile anführt und die Annahmen ihn nur belegen. */
.rowsub {
  display: block; font-size: 11px; font-weight: 400; line-height: 1.4;
  color: var(--muted); margin-top: 3px; white-space: normal;
  /* BREITE BEGRENZT: ohne Grenze zieht die Beschreibung die erste Spalte auf,
     und die Preisspalten -- der eigentliche Inhalt -- werden aus dem Bild
     gedraengt. Bei 30 Zeichen bricht sie auf zwei kurze Zeilen um und die
     Spalte bleibt so schmal wie die Namen darueber. */
  max-width: 30ch;
}

/* Zahlenzellen: linksbündig wie alle übrigen Zellen der Tabelle — sie waren
   kurz rechtsbündig, das war ein Bruch im Bild und wird nicht weiterverfolgt.
   Gleichbreite Ziffern bleiben: sie ändern die Ausrichtung nicht, lassen aber
   gleich lange Beträge gleich lang aussehen. */
/* EIN GEWICHT FUER ALLE WERTE IN DER TABELLE.
   Gemessen standen hier vier Stufen nebeneinander: Haken und guenstigster Preis
   auf 700, Vergleichspreise auf 560, Listenpreise auf 450, gewoehnlicher Text
   auf 400. Das Lauteste in der Tabelle waren damit nicht die Preise, sondern
   die 196 Haken — dieselbe Strichstaerke wie die Auszeichnung des tiefsten
   Werts, also trug 700 an einer Stelle «das ist der guenstigste» und an 196
   anderen nur «ja».
   Jetzt tragen alle Werte 400. Was wichtig ist, entscheidet der Leser mit den
   Filtern; die Tabelle stuft nicht vor. */
table.cmp td.num {
  font-variant-numeric: tabular-nums; font-feature-settings: 'tnum' 1;
}
/* Preis dieses Musterbetriebs über dem obersten Listenpreis des Anbieters.
   Gepunktete Unterstreichung und Hilfezeiger, KEINE Farbe und kein Zeichen:
   das ist die uebliche Form für «hier steht eine Erläuterung» und liest sich
   nicht als Vorwurf. Die Tatsache steht im title, für Vorleseprogramme
   zusätzlich als unsichtbarer Text — sie darf nie nur in der Linie stecken. */
.overval {
  text-decoration: underline dotted;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: var(--muted);
  cursor: help;
}
/* TIEFSTER PREIS DER ZEILE: nicht mehr hervorgehoben. Er stand kraeftiger und
   gruen da — eine Auszeichnung, und damit eine Wertung: gruen heisst auf dieser
   Seite «gut», und der guenstigste Preis ist nicht der beste. Welcher Wert der
   tiefste ist, sieht man in einer Zahlenzeile ohnehin.
   Die Klasse bleibt im Quelltext: sie traegt fuer Vorleseprogramme den Hinweis
   «guenstigster», und der ist KEIN Gegenstueck zur Farbe, sondern ein Ersatz
   fuers Ueberfliegen einer Zeile, das mit den Augen geht und mit dem Ohr nicht.
   Die Probe pruefe-musterfaelle.js prueft die Klasse ebenfalls. */
/* «nicht erhoben» in einer Zahlenzeile: gedämpft, damit die Zahlen die Fläche
   behalten. Es ist das Fehlen eines Werts, nicht selbst einer. */
table.cmp td.nonum { color: var(--muted); font-size: 12px; font-weight: 400; }

/* Der Haken traegt jetzt dieselbe Strichstaerke wie jeder andere Wert. Er darf
   dafuer etwas groesser sein: ein Haken ist ein duennes Zeichen und verschwaende
   auf Textgroesse. Groesse ist keine Wertung, Gewicht war eine. */
.yes  { color: var(--good); font-size: 15px; line-height: 1; }
.no   { color: var(--line-hard); }
.part { color: var(--muted); }

.meter { display: inline-flex; gap: 3px; vertical-align: middle; }
.meter i { width: 18px; height: 5px; border-radius: 2px; background: var(--line-hard); display: block; }
.meter i.on { background: var(--accent); }
.meter.m0 i.on { background: var(--line-hard); }

.legend {
  display: flex; flex-wrap: wrap; gap: 6px 20px; font-size: 11.5px; color: var(--muted);
  padding: 12px 2px 0;
}
.legend b { color: var(--ink-soft); }
.legend-sep { border-left: 1px solid var(--line-hard); padding-left: 20px; }

/* Die Kartenansicht ist im Juli 2026 entfallen, mit ihr .grid, .pcard, .pc-*,
   .mk, die Etiketten .tag/.tag-ch und der Ansichtsumschalter .seg. Geblieben ist
   .pth-owner: das Etikett im Tabellenkopf, gesteuert von SHOW_OWNER_TAG.
   Gefüllt statt umrandet — es ist ein Hinweis, keine Auszeichnung. */
.pth-owner {
  background: var(--ink); border-color: var(--ink); color: var(--bg);
  font-size: 9.5px; letter-spacing: .05em; text-transform: uppercase; font-weight: 650;
  padding: 2px 5px; border-radius: 3px; white-space: nowrap; flex: none;
}

.empty { text-align: center; padding: 70px 20px; border: 1px dashed var(--line-hard); border-radius: var(--r); background: var(--surface-2); }
.empty h2 { font-size: 19px; }
.empty p { color: var(--muted); max-width: 50ch; margin: 10px auto 20px; font-size: 14px; }

/* ---------------------------------------------------------------- unten */
.sub {
  font-size: 12px; letter-spacing: .11em; text-transform: uppercase; color: var(--muted);
  font-weight: 650; padding-bottom: 10px; border-bottom: 1px solid var(--line-hard); margin-bottom: 20px;
}
/* Methodik: Kennzahlenleiste. Die Zahlen werden am Bau gezählt (methodZahlen in
   build.js) und stehen vor dem Text, weil «wie viel ist erhoben» die erste Frage
   ist, die ein Leser an eine Methodik stellt. Keine Kästen, nur eine Grundlinie
   — es sind Angaben, keine Kacheln zum Anklicken. */
.meth-stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 18px 26px; margin-top: 26px; padding-top: 20px; border-top: 1px solid var(--line-hard);
}
.meth-stat b {
  display: block; font-size: clamp(24px, 3vw, 32px); font-weight: 600;
  letter-spacing: -.02em; font-variant-numeric: tabular-nums; line-height: 1.05;
}
.meth-stat span { display: block; margin-top: 5px; font-size: 12.5px; color: var(--muted); line-height: 1.35; }

/* Die Preisquellen unter «Warum manche Preisfelder leer bleiben». Gerendert aus
   vals.priceSrc, damit Erklärung und Tabellenlegende nicht auseinanderlaufen. */
.meth-list { margin: 10px 0 0; padding-left: 20px; font-size: 14px; color: var(--ink-soft); }
.meth-list li { margin-top: 4px; }

/* Fusszeile: schlanke Zeile am unteren Fensterrand. Sie liegt AUSSERHALB von
   .scroll und ist damit immer sichtbar; flex: none, damit sie ihre Höhe behält
   und der Scrollbereich den Rest bekommt. Nur eine Zeile — was dauernd zu sehen
   ist, darf keine Höhe fressen. */
/* flex: none, damit die Zeile ihre Höhe behält und nicht mitwächst.
   position: sticky mit left: 0 aus demselben Grund wie bei .below — scrollt eine
   breite Vergleichstabelle waagrecht, bleibt die Fusszeile an ihrem Platz. */
.site-foot {
  flex: none; position: sticky; left: 0;
  padding: 9px 0 10px;
  border-top: 1px solid var(--bar-line); background: var(--bar-bg);
}
.foot-inner {
  display: flex; flex-flow: row wrap; align-items: baseline; gap: 0 8px;
  width: 100%; font-size: 12px; color: var(--bar-muted); line-height: 1.5;
}
.foot-brand { font-weight: 600; color: var(--bar-ink); }
.foot-dot { color: var(--bar-line); }
/* Die Verweise rechts als Gruppe an den rechten Rand: margin-left: auto nur auf
   dem ERSTEN. space-between würde alle Teile über die ganze Breite
   auseinanderziehen und Marke und Stand trennen.
   Alle drei tragen denselben Abstand — 8px Lücke plus 10px Rand = 18px,
   gleichmässig. Vorher hatte nur das Paar Impressum/Datenschutz einen
   Trennpunkt, was 18 · 8 · 18 ergab. */
.foot-nav { margin-left: 10px; }
.foot-nav-first { margin-left: auto; }
/* Gestrichelt unterstrichen: die Verweise sollen als Verweise erkennbar sein,
   ohne dass eine durchgezogene Linie in der dunklen Leiste zu laut wird. Beim
   Ueberfahren wird die Linie durchgezogen — das ist die Rueckmeldung. */
.site-foot a { color: var(--bar-muted); text-decoration: none; }
.site-foot a:hover { color: var(--bar-ink); text-decoration: underline; }

/* ---------------------------------------------------------------- drawer */
.overlay { position: fixed; inset: 0; background: rgba(10,12,14,.5); z-index: 50; backdrop-filter: blur(2px); }
.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; width: min(560px, 100%); z-index: 60;
  background: var(--surface); border-left: 1px solid var(--line-hard);
  box-shadow: var(--shadow-lg); display: flex; flex-direction: column;
  animation: slideIn .2s cubic-bezier(.22,.7,.3,1);
}
@keyframes slideIn { from { transform: translateX(24px); opacity: .4; } to { transform: none; opacity: 1; } }
.drawer-head { display: flex; align-items: flex-start; gap: 16px; padding: 22px 24px 16px; border-bottom: 1px solid var(--line); }
.drawer-kicker { font-size: 11.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); font-weight: 600; }
.drawer-head h2 { font-size: 24px; margin-top: 4px; }
.close {
  margin-left: auto; width: 32px; height: 32px; flex: none; border-radius: 50%;
  border: 1px solid var(--line-hard); background: var(--surface); cursor: pointer;
  font-size: 19px; line-height: 1; color: var(--muted);
}
.close:hover { border-color: var(--accent); color: var(--accent); }
.drawer-body { overflow-y: auto; padding: 20px 24px 40px; }
.dsec { margin-bottom: 24px; }
.dsec > h3 {
  font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--muted);
  font-weight: 650; padding-bottom: 7px; border-bottom: 1px solid var(--line); margin-bottom: 11px;
}
.dsec p { font-size: 14.5px; color: var(--ink-soft); }
.dlist { list-style: none; margin: 0; padding: 0; }
.dlist li { font-size: 14px; color: var(--ink-soft); display: flex; gap: 9px; margin-bottom: 6px; }
.dlist li::before { content: "+"; color: var(--good); font-weight: 700; flex: none; }
.dlist.minus li::before { content: "–"; color: var(--accent); }
.dtable { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.dtable td { padding: 7px 0; border-bottom: 1px solid var(--line); vertical-align: top; }
.dtable td:first-child { color: var(--muted); width: 48%; }
.dfit { display: grid; gap: 7px; }
.dfit-row { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 12px; font-size: 13.5px; }
.dfit-row span { color: var(--ink-soft); }
.drawer-cta { display: flex; gap: 10px; flex-wrap: wrap; }
/* Der Zusatz ueber dem Datenblatt: er sagt, welche Zeilen darin stehen. Im
   Panel nur die der Kategorie, auf der Produktseite alle. Beides ist richtig,
   der Unterschied stand bisher nirgends. */
.dnote { margin: 0 0 9px; font-size: 12px; line-height: 1.5; color: var(--muted); }

/* ---------------------------------------------------------------- responsive */
@media (max-width: 1360px) {
  .head-meta span:nth-child(2) { display: none; }
}
@media (max-width: 1180px) {
  .head-meta { display: none; }
  #priceRange { width: 96px; }
  .dd-val { max-width: 150px; }
}
@media (max-width: 780px) {
  .dd-panel-wide { min-width: 0; width: calc(100vw - 28px); grid-template-columns: 1fr; }
}
@media (max-width: 760px) {
  .wrap { padding: 0 14px; }
  .wrap-pad { padding: 0 14px; }
  th.corner, td.rowlab { width: 138px; min-width: 138px; }
  table.cmp thead th { min-width: 152px; padding: 10px 11px; }
  table.cmp td { padding: 7px 11px; }
  .pth-short { display: none; }
  .pth-name { font-size: 14.5px; }
  .legend-sep { border: 0; padding-left: 0; }
}
/* TELEFON. Die Masse sind nicht geschmackvoll gerundet, sondern gerechnet: auf
   den 390px eines gaengigen Geraets muessen die Kriterienspalte und ZWEI
   Anbieterspalten gleichzeitig sichtbar sein — 112 + 2 x 130 = 372.

   Warum zwei und nicht eine: mit einer sichtbaren Spalte ist die Seite kein
   Vergleich mehr, sondern ein Datenblatt, durch das man waagrecht blaettert.
   Der Leser muesste sich den Wert der ersten Spalte merken, um ihn mit der
   zweiten zu vergleichen — genau die Arbeit, die die Tabelle abnehmen soll.
   Enger als 130px geht nicht: «CHF 35–119/Mt.» ist die laengste Zelle und
   braucht mit Polsterung so viel. */
@media (max-width: 560px) {
  table.cmp { font-size: 12.5px; }
  table.cmp td { padding: 7px 9px; }
  .pth-name { font-size: 13.5px; }

  /* FESTE SPALTEN STATT INHALTSBREITE. Das war der eigentliche Hebel, und er
     lag nicht dort, wo er zu liegen schien.

     .table-scroll ist «width: max-content». Damit bekommt JEDE Spalte die
     Breite ihres laengsten Inhalts in EINER Zeile — und zwar auch dann, wenn
     der Inhalt umbrechen duerfte, denn max-content misst grundsaetzlich
     ungebrochen. Umbruch zu erlauben aendert daran nichts; gemessen blieb die
     bexio-Spalte auf 171px, gehalten von «bexio AG (Mobiliar-Gruppe)».

     Mit table-layout: fixed zaehlen nur noch die Breitenangaben der ersten
     Zeile, und der Inhalt richtet sich danach statt umgekehrt. 112 + 2 x 130 =
     372 passen damit wirklich auf 390px. Auf breiten Schirmen bleibt es beim
     automatischen Layout: dort ist Inhaltsbreite die bessere Wahl, weil Platz
     da ist und ein unnoetiger Umbruch nur Zeilen hoeher macht.

     overflow-wrap: anywhere ist die Sicherung. Bei festen Spalten kann ein
     langes Wort ohne Trennstelle sonst ueber den Zellenrand in die Nachbarzelle
     laufen — ein Produktname wie «Buchhaltungssoftware» tut genau das. */
  /* 112 UND NICHT 124: mit 124 ergaben Kriterienspalte und zwei Anbieter
     zusammen 386 von 390 Pixeln. Vom dritten Anbieter waren null Pixel zu
     sehen, und die Tabelle sah aus, als endete sie nach zwei Spalten — bei
     dreissig. Mit 112 bleiben rund 28px des dritten sichtbar. Ein angeschnittener
     Inhalt ist der uebliche und beste Hinweis darauf, dass es seitwaerts
     weitergeht: er braucht keinen Text und keinen Pfeil.
     Enger geht es nicht — «CHF 35–119/Mt.» misst mit Polsterung 108px. */
  table.cmp td { white-space: normal; overflow-wrap: anywhere; max-width: 112px; }
  /* min-width MUSS hier mit weg. Bei festen Spalten gilt trotzdem die
     Untergrenze der Kopfzelle, und die stand aus den breiteren Stufen auf
     152px — die Breitenangabe darunter blieb wirkungslos, gemessen 136px je
     Spalte statt der gesetzten 124.

     Und beide Regeln brauchen dieselbe Kraft wie «table.cmp thead th»: mit dem
     kuerzeren «th.corner» haette die allgemeinere Regel gewonnen und die
     Kriterienspalte waere so breit wie eine Anbieterspalte. */
  table.cmp thead th { min-width: 0; max-width: 112px; padding: 10px 9px; overflow-wrap: anywhere; }
  /* Der Produktname bricht NUR an Leerzeichen. «anywhere» erbt er sonst von der
     Kopfzelle und wird zu «Banana Buchhaltu / ng Plus» — ein Eigenname, den man
     mitten im Wort trennt, liest sich wie ein Tippfehler. «break-word» greift
     erst, wenn ein einzelnes Wort wirklich nicht passt.

     UND HOECHSTENS ZWEI ZEILEN. In einer Tabelle sind alle Kopfzellen gleich
     hoch, also bestimmt der laengste Name die Hoehe fuer alle: «Dynamics 365
     Business Central» braucht in einer 112px-Spalte vier Zeilen und zog den
     Kopf auf 142px — ein Sechstel des Bildschirms, auf jeder Kategorieseite,
     wegen eines einzigen Anbieters. Zwei Zeilen fassen rund 24 Zeichen und
     benennen die Spalte eindeutig; der vollstaendige Name steht in der
     Kopfzelle als title-Attribut und auf der Produktseite, die der Name
     verlinkt. */
  .pth-name {
    overflow-wrap: break-word;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden;
  }
  /* UND EINE EIGENE ZEILE FUER DEN NAMEN. In der Kopfzeile stehen drei Dinge
     nebeneinander: Auswahlzeichen, Name, Kreuz zum Wegnehmen. Bei 112px Spalte
     blieben dem Namen nach Polsterung, Zeichen und Abstaenden noch 48px — und
     dann brach selbst «ClubDesk» zu «ClubDe / sk». Ein Produktname, der mitten
     im Wort umbricht, ist keine Beschriftung mehr.
     Auf einer eigenen Zeile hat er die vollen 94px. Die Zeile kostet nichts:
     die beiden Zeichen sind flach, und der Kopf wird dadurch nicht hoeher als
     mit vier gebrochenen Namenszeilen. */
  .pth-row { flex-wrap: wrap; row-gap: 3px; }
  .pth-name { flex: 1 0 100%; order: 2; }

  /* Haarlinie zwischen den Kopfzellen. Das Kreuz zum Wegnehmen sitzt am rechten
     Rand seiner Zelle und stand damit unmittelbar neben dem Auswahlzeichen der
     NAECHSTEN Spalte — auf drei nebeneinanderliegenden Spalten ohne Trennung
     las sich die Reihe als «× ○ KLARA» statt als «○ KLARA … ×». Die Linie
     ordnet jedes Zeichen wieder seiner Spalte zu; auf breiten Schirmen ist der
     Abstand gross genug und sie bleibt weg. */
  table.cmp thead th + th { border-left: 1px solid var(--line); }
  table.cmp thead th.corner { min-width: 0; max-width: 106px; }
  table.cmp tbody td.rowlab { max-width: 106px; }
  /* Der Zusatz unter «Preisspanne» nennt die Skala des Balkens. Er ist die
     laengste Beschriftung der ersten Spalte und hielt sie allein auf 151px —
     fuer eine Erlaeuterung des Balkens, der daneben ohnehin zu sehen ist. */
  .scalenote { display: none; }

  /* ---- Spaltenweise einrasten -------------------------------------------
     Bisher war die Bedienung der Tabelle auf dem Telefon ein einziger langer
     Schiebebalken ueber 3386 Pixel. Wo man loslaesst, steht man — und man
     steht fast immer mitten in einer Spalte: gemessen war der erste Preis
     nach einem Wisch als «HF 9–49/Mt.» zu lesen, weil die klebende
     Kriterienspalte seinen Anfang verdeckte.

     scroll-snap laesst jeden Wisch auf einer Spaltenkante enden. Aus dem
     Ziehen eines Blattes wird ein Blaettern von Anbieter zu Anbieter — und
     genau das ist die Bewegung, die der Leser meint.

     scroll-padding-left haelt dabei die Breite der klebenden Kriterienspalte
     frei. Ohne diese Zeile rastete die Spalte buendig am linken Rand ein und
     verschwaende dort unter der Kriterienspalte — das Einrasten haette den
     Fehler nicht behoben, sondern festgeschrieben.

     «proximity» und nicht «mandatory»: mandatory zwingt bei JEDER Bewegung in
     die naechste Rastung, auch beim senkrechten Rollen mit einer leichten
     Schraeglage — die Tabelle zuckte dann seitwaerts, ohne dass jemand sie
     seitwaerts bewegen wollte. proximity rastet nur, wenn ohnehin schon fast
     eine Kante erreicht ist. */
  .scroll { scroll-snap-type: x proximity; scroll-padding-left: 138px; }
  table.cmp thead th { scroll-snap-align: start; }
  th.corner { scroll-snap-align: none; }
}

/* ------------------------------------------------------------- Tippflaechen
   Gemessen auf 390px: das «!» am Kriterium 18x18, das Auswahlzeichen einer
   Tabellenspalte 13x13, das Erklaerzeichen 15x15, eine Kriterienzeile 19px
   hoch. Mit der Maus trifft man das; mit dem Finger nicht zuverlaessig.

   ZIEL SIND 24 PIXEL und nicht die oft genannten 44. 24x24 ist das Mass, das
   die Zugaenglichkeitsrichtlinie WCAG 2.2 als Mindestgroesse fuehrt; 44 kommt
   aus Apples Gestaltungsempfehlung und waere hier teuer erkauft — vierzehn
   Kriterienzeilen zu je 44px sind 616px Blatthoehe fuer eine Liste, durch die
   man ueberblicken koennen will.

   ZWEI WEGE, je nach Element:
   — Wo Platz ist (Kriterienzeile, «!»), waechst das Element selbst.
   — Wo kein Platz ist, waechst nur die TREFFERFLAECHE ueber ein ::after. Das
     Zeichen bleibt so klein, wie es aussehen soll. Ein sichtbar vergroessertes
     Auswahlzeichen in dreissig Tabellenspalten waere eine Aufforderung, und
     genau das soll es nicht sein.

   Die Grenze ist 900px und nicht «(pointer: coarse)»: ein Fenster dieser
   Breite ist fast immer ein Telefon oder ein Tablet, und wer am Rechner sein
   Fenster so schmal zieht, dem schaden groessere Ziele nicht. */
@media (max-width: 900px) {
  .fp-row { grid-template-columns: 1fr 22px 24px; min-height: 34px; }
  .fp-lab { padding: 8px 0; font-size: 12.5px; }
  .fp-imp { width: 24px; height: 24px; font-size: 11px; }

  /* Unsichtbare Vergroesserung. inset ist negativ, die Flaeche ragt also ueber
     das Zeichen hinaus — ohne es zu verschieben, weil sie absolut liegt. */
  .info { position: relative; }
  .info::after { content: ''; position: absolute; inset: -5px; }

  /* Beim Auswahlzeichen der Spalte kommt der Abstand zum Produktnamen dazu:
     ohne ihn griffe die vergroesserte Flaeche ueber die ersten Zeichen des
     Namens, und ein Tipp auf den Namen — der auf die Produktseite fuehrt —
     landete auf dem Auswahlzeichen. Eine groessere Trefferflaeche, die eine
     andere verschluckt, ist keine Verbesserung. */
  /* Bei schmalen Spalten bricht der Produktname auf zwei bis drei Zeilen. Das
     Auswahlzeichen ist «align-self: center» und sass dann auf der MITTLEREN
     Zeile, waehrend das Kreuz zum Wegnehmen oben blieb — zwei Schalter
     derselben Spalte auf zwei Hoehen, dazwischen der Name. Beide gehoeren an
     den oberen Rand, zur ersten Zeile des Namens. */
  .pth-row { gap: 9px; align-items: flex-start; }
  .pth-pick { align-self: flex-start; margin-top: 2px; }
  .pth-pick { position: relative; }
  .pth-pick::after { content: ''; position: absolute; inset: -6px; }

  /* Dasselbe fuer das Kreuz, das eine Spalte wegnimmt — es sitzt am rechten
     Ende derselben Kopfzeile. */
  .pth-x { position: relative; }
  .pth-x::after { content: ''; position: absolute; inset: -5px; }

  /* Die Ausklappfelder («Ich bin», «Sortieren nach») waren 20px hoch. Sie
     tragen im Panel die ganze Breite, es fehlte allein die Hoehe. */
  .dd > summary { min-height: 34px; display: flex; align-items: center; }
  .fpanel .dd > summary { padding-top: 6px; padding-bottom: 6px; }
  .check { min-height: 30px; }

  /* Der Preisschieber war 16px hoch. Ihn mit dem Finger zu fassen war Glueck. */
  #priceRange { height: 24px; }

  /* Verweise IN der Tabelle — «öffnen ↗» in der letzten Zeile, 29 Stueck, und
     der Erklaerverweis an jedem Abschnittstitel. Beide waren rund 17px hoch.
     inline-block, damit die Polsterung ueberhaupt greift: an einem inline-
     Element vergroessert sie den Kasten nicht. */
  table.cmp td a, tr.grouprow .grplink { display: inline-block; padding: 4px 0; }

  /* Fusszeile: vier Verweise, die auf dem Telefon in eine zweite Reihe fallen
     und dort mit 18px Zeilenhoehe dicht uebereinander lagen. Die Polsterung
     gibt jedem seine 24px, ohne die Leiste sichtbar aufzublaehen — die Reihen
     ruecken nur um wenige Pixel auseinander. */
  .site-foot a { display: inline-block; padding: 4px 0; }
  .foot-inner { row-gap: 2px; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
  html { scroll-behavior: auto; }
}
@media print {
  .site-head { display: none; }
  /* Im Druck bleibt der Kopf: er sagt, WELCHE Kategorie das Blatt zeigt. Der
     Schweiz-Wähler nicht — er klappt auf Papier nichts auf. */
  .cat-head .chside, .cat-head .dd { display: none; }
  html, body { height: auto; overflow: visible; display: block; background: #fff; }
  /* display zurücksetzen: .scroll ist im Bildschirmlayout eine Flex-Spalte,
     die die Fusszeile bei kurzem Inhalt nach unten drückt. Im Druck gibt es
     keine Fensterhöhe, an der sich das ausrichten liesse. */
  .scroll { overflow: visible; display: block; }
  .site-foot { position: static; }
  .table-scroll { border: 0; width: auto; }
  table.cmp thead th, tr.grouprow td, td.rowlab, th.corner { position: static; }
}

/* =========================================================================
   Startseite — Kategorienwahl
   Kompakt: dreizeilige Kacheln (Name mit Anzahl, Kurzzeile, Kriterienzeile).
   Die Kriterien standen vorher als Chips unter einer eigenen Überschrift —
   das kostete zwei Zeilen je Kachel und machte die Seite bei vierzehn
   Kategorien unnötig lang.
   ========================================================================= */
.start .wrap { padding-top: 30px; padding-bottom: 34px; max-width: 1240px; }
.start-kicker {
  font-size: 11px; font-weight: 620; letter-spacing: .12em;
  text-transform: uppercase; color: var(--accent); margin-bottom: 7px;
}
.start h1 { font-size: clamp(24px, 2.9vw, 36px); }
.start-lead { margin-top: 8px; max-width: 66ch; font-size: 15px; color: var(--ink-soft); }
/* Mehr Luft nach OBEN als nach unten: eine Ueberschrift gehoert zu dem, was
   sie ankuendigt. Bei 20 oben gegen 14 unten stand sie fast mittig zwischen
   den Bloecken und schwebte. 34 gegen 14 bindet sie nach unten. */
.start-sub { margin-top: 34px; font-size: 13px; text-transform: uppercase;
             letter-spacing: .08em; color: var(--muted); font-weight: 620; }
/* GLEICHER ABSTAND ZUR UEBERSCHRIFT WIE BEI DEN GROSSEN KACHELN. Unter der
   Grundregel .sub standen 20px Aussenabstand, und die Raster brachten nochmals
   14px mit — die kleinen Kacheln hingen damit 34px unter ihrer Linie, die zwei
   grossen 8px. Zwei Abstände für dieselbe Beziehung auf einer Seite. Der
   kleinere gewinnt: er bindet die Kacheln an ihre Überschrift, statt sie
   frei darunter schweben zu lassen.
   Der Abstand NACH OBEN (34px, zum vorigen Abschnitt) bleibt gross — er ist es,
   der die Abschnitte trennt, und er muss deutlich grösser sein als der nach
   unten, sonst gehört die Überschrift optisch nach oben statt nach unten. */
.start-sub { margin-bottom: 8px; }
.start-sub + .cat-grid, .start-sub + .br-grid, .start-sub + .cat-grid-soon { margin-top: 0; }
.start-omitted { margin-top: 22px; padding-top: 13px; border-top: 1px solid var(--line); }

/* Vierte Ebene «selbst bauen»: eine ruhige Abschlusszeile statt eines vierten
   Kachelrasters. Die Gewichtung der Startseite laeuft damit abwaerts — breite
   Karten, Karten, Pillen, Schlusszeile — und springt nicht auf einen schweren
   Einzelkasten zurueck. */
/* ------------------------------------------------- Die Grundsatzfrage oben
   Zwei Kacheln nebeneinander: kaufen oder bauen. Links gefuellt, rechts nur
   umrandet — «selbst bauen» ist der Weg der Minderheit und muss gleich
   sichtbar sein, nicht gleich laut. Unter 720px untereinander, Kaufen zuerst:
   zwei gequetschte Kacheln nebeneinander sind schlechter als zwei volle
   untereinander. */
.fork { display: grid; grid-template-columns: 1fr; gap: 8px; margin-top: 14px; }
/* ZWEI DRITTEL ZU EINEM DRITTEL, nicht halbe-halbe. Links steht die Antwort für
   die meisten Betriebe, rechts die für die wenigen, denen nichts passt. Gleich
   grosse Kacheln stellten die zwei als gleichwertige Wahl hin; die Breite sagt
   den Rang, ohne dass ein Wort ihn behaupten muss.
   Erst ab 720px: darunter stehen die Kacheln ohnehin untereinander, und ein
   Drittel von 380px trüge die Überschrift «Software selbst bauen» nicht.

   GESETZTE PLAETZE, nicht der natürliche Fluss. Im Quelltext stehen die vier
   Teile in der Reihenfolge des schmalen Schirms: Titel, Kachel, Titel, Kachel.
   Ohne Zuweisung landeten sie im Raster als Titel | Kachel / Titel | Kachel —
   also nebeneinander statt übereinander. Die vier Regeln stellen die Spalten
   auf: erste zwei links, letzte zwei rechts.
   Die Titelzeile ist EINE Rasterzeile für beide Spalten. Damit stehen die zwei
   Titel auf derselben Grundlinie, auch wenn der linke umbricht und der rechte
   nicht — und die Kacheln beginnen darunter trotzdem auf gleicher Höhe. */
@media (min-width: 720px) {
  /* BREITERE RINNE ALS 8px. Solange die zwei Kacheln unter EINER Überschrift
     standen, war der Abstand der Kachelabstand — 8px, wie im Kachelraster
     darunter. Jetzt sind es zwei Abschnitte mit je eigener Überschrift und
     eigener Grundlinie, und zwei Linien in 8px Abstand lesen sich als EINE
     Linie mit einer Kerbe. 24px trennen sie zu zwei Abschnitten; mehr wäre ein
     Loch in der Seite. Nur waagrecht: senkrecht bleiben es 8px, sonst stünde
     der Titel von seiner eigenen Kachel weiter weg als von der fremden. */
  .fork { grid-template-columns: 2fr 1fr; grid-template-rows: auto 1fr; column-gap: 24px; }
  .fork > :nth-child(1) { grid-area: 1 / 1; }
  .fork > :nth-child(2) { grid-area: 2 / 1; }
  .fork > :nth-child(3) { grid-area: 1 / 2; }
  .fork > :nth-child(4) { grid-area: 2 / 2; }
}
/* Der Aussenabstand der Ueberschrift sitzt im Raster falsch: er stammt aus der
   Zeit, als sie ueber dem Raster stand und ihn gegen den Text darueber
   brauchte. Innerhalb des Rasters trennt sie die Rasterluecke von der Kachel.
   Der Abstand nach oben bleibt und wandert an .fork selbst. */
/* h2 IM WAEHLER, nicht nur die zwei Klassen: weiter unten steht
   .start-sub:first-of-type mit margin-top: 24px, und das traf von den beiden
   Titeln nur den linken — er sass dadurch 24px tiefer als der rechte, zwei
   Ueberschriften nebeneinander auf zwei Linien. Bei gleicher Spezifitaet
   gewinnt die spaetere Regel; das Element im Waehler hebt diese hier darueber.
   Derselbe Fallstrick ist im Stilblatt schon dreimal vermerkt (.chside,
   .wrap-full.layout, .dd-cats). */
.fork { margin-top: 34px; }
.fork > h2.fork-sub { margin-top: 0; margin-bottom: 0; }
/* Keine Mindesthoehe. 148px liessen unter dem kurzen Text der linken Kachel
   eine leere Zeile stehen — die Kachel soll so hoch sein wie ihr Inhalt.
   Gleich hoch werden die beiden trotzdem: das Raster streckt sie, und
   margin-top:auto haelt die Fusszeile beider auf derselben Linie. */
.fork-card {
  display: flex; flex-direction: column; gap: 3px;
  padding: 12px 15px 13px; border-radius: var(--r); text-decoration: none;
  border: 1px solid var(--line-hard); background: var(--surface); color: var(--ink);
}
/* WENN DER ZEIGER AUF DER MARKE STEHT, reagiert die Kachel NICHT. Der Klick
   trifft dort die Marke und nicht die Kachel — eine Kachel, die trotzdem
   aufleuchtet, verspricht eine Handlung, die nicht ausgeloest wird. Mit :not()
   statt Zuruecksetzen: so muss die Regel die Grundwerte nicht kennen. */
.fork-card:hover:not(:has(.cat-mk:hover)) { color: inherit; border-color: var(--accent); box-shadow: var(--shadow-lg); }
.fork-card { position: relative; }
/* Kopfzeile der Gesamtloesungskacheln: Fahne, Name, Anzahl rechts aussen —
   dieselbe Anordnung wie bei den Bereichskacheln darunter. */
.fork-top { display: flex; align-items: center; gap: 8px; }
.fork-h { flex: 1; font-size: 18px; font-weight: 640; letter-spacing: -.01em; }
.fork-n {
  flex: none; font-size: 15px; font-weight: 660; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* Auf Lesebreite umbrechen statt ueber die ganze Kachel laufen: dieselben
   Texte wie unten ergeben so auch hier zwei Zeilen, und eine Zeile ueber
   700 Pixel liest sich ohnehin schlecht. Feste Hoehe, damit die zwei Kacheln
   gleich hoch bleiben, auch wenn ein Text kuerzer ist. */
/* DREI ZEILEN STATT ZWEI, und keine FESTE Höhe mehr. Die feste Höhe hielt die
   zwei Kacheln gleich hoch, als sie nebeneinander im Fluss standen. Seit sie
   gesetzte Rasterplätze in einer 1fr-Zeile haben, macht das Raster sie ohnehin
   gleich hoch — die Angabe schnitt nur noch Text ab. Die dritte Zeile trägt,
   was die grosse Kachel jetzt sagt: nicht bloss WAS in einem System steckt,
   sondern WARUM eines besser ist als drei. max-height bleibt als Notausgang,
   falls ein Text in einer Sprache länger gerät. */
.fork-p {
  font-size: 13px; line-height: 1.45; color: var(--ink-soft);
  max-height: calc(3 * 1.45em); overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
}
/* Die uebrigen Gesamtloesungen: eine leise Zeile, keine dritte Kachel. */
.fork-also { margin-top: 10px; font-size: 12.5px; color: var(--muted); }
.fork-also a { color: var(--ink-soft); }
.fork-also a:hover { color: var(--accent); }
.start-disc { max-width: 82ch; margin-top: 10px; font-size: 14px; color: var(--ink-soft); }

.cat-grid {
  display: grid; gap: 8px; margin-top: 14px;
  grid-template-columns: repeat(auto-fill, minmax(min(224px, 100%), 1fr));
}
.cat-grid-soon { margin-top: 14px; }

.cat-card {
  display: flex; flex-direction: column; gap: 5px;
  padding: 9px 12px 10px; text-decoration: none; color: inherit;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-s); box-shadow: var(--shadow);
  transition: border-color .14s ease, box-shadow .16s ease;
  /* Bezug für die Ecke oben rechts (.cat-badge). */
  position: relative;
}

/* Marken oben rechts auf der Kachel: «Demo» (erfundener Bestand) und «managed»
   (Dienstleistung statt Software). Beide in einer Gruppe, absolut gesetzt statt
   in .cat-top — dort nähmen sie der Überschrift Platz und brächen bei langen
   Namen um.

   «Demo» behält seine dunkle Fläche und steht ZUERST: es ist eine Warnung, keine
   Einordnung. «managed» bleibt zurückhaltend und ohne Rot — Rot heisst auf dieser
   Seite «von Ihnen gewählt» und in der Schweiz-Leiste «zwingende Anforderung».
   Eine dritte Bedeutung würde beide entwerten. */
.cat-badges {
  position: absolute; top: 9px; right: 11px;
  display: flex; align-items: center; gap: 5px;
}
/* AUSPROBIERT UND VERWORFEN, am 30. Juli 2026: das Etikett stand kurz als
   gefüllte Fläche da, erst in --ink, dann zurückgenommen in --muted. Beides
   war zu laut. Gefüllt zieht es den Blick vor den Namen, den es begleitet —
   und es benennt eine Sorte, keine Warnung. Die Warnung auf dieser Seite ist
   «Demo», und die hat ihre dunkle Fläche aus gutem Grund allein.
   Also wieder die dezente Fassung: gedämpfte Schrift, heller Grund, Rand. */
.cat-badge {
  font-size: 9.5px; letter-spacing: .07em; text-transform: uppercase; font-weight: 660;
  color: var(--muted); background: var(--surface-2);
  border: 1px solid var(--line-hard); border-radius: 3px;
  padding: 2px 5px; white-space: nowrap;
}
.cat-card:hover:not(:has(.cat-mk:hover)) .cat-badge { color: var(--accent); border-color: var(--accent); }
/* ZWEI MARKEN AUS EINER FAMILIE: Schweizer Kreuz und Globus, beide als
   Strichzeichnung in derselben Staerke. Eine rote Flaeche neben einer grauen
   Linie liest sich als Auszeichnung gegen Fussnote — sie sollen aber zwei
   gleichwertige Zustaende sein. Rot bleibt darum den beiden Bedeutungen
   vorbehalten, die es auf dieser Seite schon hat. */
/* Der Globus so gross wie die Fahne, und der Strich mit ihr mitgewachsen —
   sonst wirkt der Kreis neben der gefuellten Flaeche duenn und weiter weg. */
.cat-ico {
  flex: none; width: 13px; height: 13px;
  fill: none; stroke: var(--muted); stroke-width: 1.05;
}
/* Die echte Fahne: quadratisch mit gleicharmigem Kreuz, aber in Schwarzweiss
   statt Rot. Die Balken sind in der Flaechenfarbe der Kachel ausgespart, nicht
   weiss gemalt — so stimmt es auch im dunklen Farbschema. */
/* Groesser und mit kraeftigeren Balken: bei zehn Pixeln und duennen Strichen
   las sich das Quadrat als Pluszeichen. Die Fahne haelt die amtlichen
   Verhaeltnisse — Balkenbreite ein Sechstel der Seite, Arme ein Sechstel
   ueber die Mitte hinaus. */
.cat-flag {
  flex: none; position: relative; width: 13px; height: 13px; border-radius: 2px;
  background: var(--ink-soft);
}
.cat-flag span { position: absolute; background: var(--surface); border-radius: .5px; }
.cat-flag span:nth-child(1) { left: 5.4px; top: 2.4px; width: 2.2px; height: 8.2px; }
.cat-flag span:nth-child(2) { left: 2.4px; top: 5.4px; width: 8.2px; height: 2.2px; }
/* Der Kachel-Hover faerbt die Marke NICHT mehr mit ein: sonst faellt nicht
   auf, dass sie ein eigenes Ziel hat. */
.cat-pill .cat-ico, .cat-pill .cat-flag { margin-right: -1px; }
/* Die Marke ist ein eigener Verweis in die rote Leiste und liegt darum ueber
   der Klickflaeche der Kachel. Der Trefferbereich ist absichtlich groesser als
   das Zeichen — 13 Pixel allein waeren auf dem Telefon nicht zu treffen. */
.cat-mk {
  position: relative; z-index: 2; flex: none; display: inline-flex;
  align-items: center; margin: -6px 0 -6px -5px; padding: 6px 5px;
  border-radius: 5px; text-decoration: none;
  transition: background-color .12s ease, box-shadow .12s ease;
}
/* Deutlicher Hinweis, dass die Marke selbst ein Ziel ist: eigene Flaeche und
   ein Ring in der Akzentfarbe. Ohne ihn sieht das Zeichen aus wie ein Symbol
   und niemand kommt auf die Idee, darauf zu klicken. */
.cat-mk:hover, .cat-mk:focus-visible {
  background: var(--surface);
  box-shadow: 0 0 0 1.5px var(--accent), 0 1px 3px rgba(20,22,26,.10);
  outline: none;
}
.cat-mk:hover .cat-flag, .cat-mk:focus-visible .cat-flag { background: var(--accent); }
.cat-mk:hover .cat-ico,  .cat-mk:focus-visible .cat-ico  { stroke: var(--accent); }
.cat-why {
  position: relative; z-index: 2; align-self: stretch;
  display: flex; align-items: center; gap: 7px;
  margin: auto -15px 0; padding: 8px 15px 0;
  border-top: 1px solid var(--line);
  font-size: 11.5px; line-height: 1.25; color: var(--muted); text-decoration: none;
}
/* Gestrichelt unterstrichen — nur der Text, nicht der Pfeil: eine Linie unter
   dem Pfeil sieht aus wie ein Zeichensetzungsfehler. Beim Ueberfahren wird sie
   durchgezogen, das ist die Rueckmeldung. */
.cat-why > span {
  text-decoration: underline dashed; text-underline-offset: 3px;
  text-decoration-thickness: 1px; text-decoration-color: var(--line-hard);
}
.cat-why > span::after { content: " →"; text-decoration: none; }
.cat-why:hover { color: var(--accent); }
.cat-why:hover > span { text-decoration: underline solid; text-decoration-color: currentColor; }
/* Flaechendeckender Verweis der Kachel: er liegt UNTER Marke und Verweis. */
.cat-card { position: relative; display: flex; flex-direction: column; }
.cat-lnk::after { content: ""; position: absolute; inset: 0; border-radius: inherit; z-index: 1; }
.cat-lnk:focus-visible::after { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Nur fuer Vorlesegeraete: der Name als zugaenglicher Name des Verweises. */
.vh {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
.chside-nurch { margin-top: 10px; padding-top: 10px; border-top: 1px solid rgba(255,255,255,.22); }
/* Der Schluessel unter dem Einleitungssatz: Zeichen und Bezeichnung als Paare,
   danach der Hinweis. Nicht als Kasten — er soll erklaeren, nicht auffallen —,
   aber mit Linie darueber, damit er sich vom Lead loest. */
/* Als eigene Flaeche abgesetzt, nicht als weitere Textzeile: der Schluessel
   erklaert die Seite, er gehoert nicht zum Fliesstext. Andere Fuellung als die
   Kacheln (--surface-2 statt --surface), damit er nicht wie eine weitere
   Kachel aussieht — er ist eine Erklaerung, kein Ziel.
   Die zwei Zeichen stehen in eigenen Feldern mit heller Flaeche: so liest man
   sie als Muster, das man in den Kacheln wiedererkennt. */
.start-key {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 9px;
  margin-top: 14px; padding: 7px 12px;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--r-s); font-size: 12.5px; color: var(--ink-soft);
}
/* Keine Pillen: der Schluessel erklaert Zeichen, er bietet nichts zum
   Anklicken an. Ein umrandetes Oval sieht aus wie eine Schaltflaeche und
   verspricht damit etwas, was es nicht einloest. Zeichen und Wort genuegen,
   die Fettung trennt sie vom Hinweis daneben. */
.key-item {
  display: inline-flex; align-items: center; gap: 7px;
  font-weight: 600; color: var(--ink);
}
.key-hint { flex: 1 1 30ch; color: var(--muted); }
/* Damit der Kategoriename nicht unter die Marken läuft. Grosszügig gerechnet:
   zwei Marken plus Abstand brauchen mehr Platz als eine. */
.cat-card-marked .cat-top { padding-right: 118px; }
.cat-card:hover:not(:has(.cat-mk:hover)) { color: inherit; border-color: var(--accent); box-shadow: var(--shadow-lg); }
/* Mittig statt auf der Grundlinie: die Fahne ist ein Quadrat ohne
   Schriftlinie und stand darum tief. Enger auch — Fahne und Name
   gehoeren zusammen, die Anzahl rechts wird ohnehin weggeschoben. */
.cat-top { display: flex; align-items: center; gap: 7px; }
/* Der Name bricht in keiner Kachel um: eine zweizeilige Ueberschrift macht
   die ganze Reihe hoeher und das Raster unruhig. Wo ein Name zu lang ist,
   steht in den Sprachdateien ein Kurzname (cats[].kachel); die Kuerzung mit
   Auslassungspunkten ist nur das Netz darunter. */
.cat-card h2 {
  font-size: 14px; flex: 1; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cat-n {
  flex: none; font-size: 15px; font-weight: 660; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.cat-card:hover:not(:has(.cat-mk:hover)) .cat-n { color: var(--accent); }
/* Zwei Zeilen Beschreibung auf jeder Kachel, darunter eine Zeile
   Produktnamen. Feste Hoehe statt Klammerung allein: so sind alle Koerper
   gleich hoch, auch wenn ein Text nur eine Zeile braucht — sonst waere der
   Fuss von Kachel zu Kachel um eine Zeile versetzt. */
.cat-tag {
  font-size: 12px; color: var(--ink-soft); line-height: 1.4;
  height: calc(1 * 1.4em); overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical;
}
/* Produktnamen statt Kriterienliste. Die schweizerischen Kriterien stehen auf
   der Kategorieseite — dort landet, wer über die Suche kommt, und dort haben
   sie den Platz, erklärt zu werden. */
.cat-names {
  margin-top: 1px; font-size: 12px; line-height: 1.45; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Ebene 1: eine breite Karte, die über die ganze Reihe läuft */
.cat-grid-wide { grid-template-columns: repeat(auto-fit, minmax(min(420px, 100%), 1fr)); }
.cat-card-wide { padding: 15px 17px 16px; }
.cat-card-wide h2 { font-size: 17px; }

/* Ebene 3: Branchen als VERZEICHNIS, nicht als Pillenwand.
   Vier Spalten mit Gruppenueberschrift, in jeder eine Liste. Ein Verzeichnis
   darf anders aussehen als eine Kachel — es ist etwas anderes: kein Vorschlag,
   sondern ein Register, das man von A nach Z absucht. */
.br-grid {
  display: grid; grid-template-columns: 1fr; gap: 14px 28px; margin-top: 14px;
}
@media (min-width: 700px)  { .br-grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1040px) { .br-grid { grid-template-columns: repeat(4, 1fr); } }
.br-h {
  font-size: 11px; font-weight: 660; letter-spacing: .09em; text-transform: uppercase;
  color: var(--muted); padding-bottom: 7px; border-bottom: 1px solid var(--line);
}
/* Die Zeile ist der Verweis: volle Breite, damit auch die Zahl anklickbar ist
   und der Zeiger nicht zwischen Name und Anzahl abreisst. */
.br-row {
  position: relative; display: flex; align-items: center; gap: 8px;
  padding: 4px 0; border-bottom: 1px solid var(--line);
  color: var(--ink); font-size: 13.5px;
}
/* Flaechendeckender Verweis: die ganze Zeile fuehrt in die Kategorie, auch
   die Zahl rechts — nur die Marke hat ihr eigenes Ziel. */
.br-lnk { flex: 1; min-width: 0; text-decoration: none; color: inherit; }
.br-lnk::after { content: ""; position: absolute; inset: 0; z-index: 1; }
.br-lnk:focus-visible::after { outline: 2px solid var(--accent); outline-offset: 2px; }
.br-row:hover:not(:has(.cat-mk:hover)) { color: var(--accent); }
.br-n {
  flex: none; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums;
}
.br-row:hover:not(:has(.cat-mk:hover)) .br-n { color: var(--accent); }


/* Kategorie ohne Bestand: KEINE eigene Kennzeichnung auf der Startseite —
   Entscheid des Betreibers. Die Kachel sieht aus wie jede andere, sie zeigt nur
   keine Anzahl. Dass noch keine Tabelle wartet, sagt erst .soon-note auf der
   Kategorieseite. */
/* ------------------------------------------------------------- Produktseite
   Das Detailpanel als Seite — aber nicht als dieselbe schmale Spalte. Eine
   Lesespalte von 78 Zeichen mitten in einem breiten Fenster liess links und
   rechts je vierhundert Pixel leer, und der Steckbrief zog die Seite darunter
   in die Länge. Jetzt zwei Spalten: links der Lesetext in lesbarer Breite,
   rechts das Zahlenwerk, das ohnehin in Zeilen steht. Beides zusammen nutzt
   die Breite, ohne den Fliesstext unlesbar breit zu machen.
   Ab 1040px zweispaltig, darunter untereinander in der Reihenfolge von
   assets/detail.js. */
.prog-wrap { padding-top: 26px; padding-bottom: 48px; max-width: 1180px; }
.prog-grid { margin-top: 4px; }
@media (min-width: 1040px) {
  .prog-grid {
    display: grid; grid-template-columns: minmax(0, 62ch) minmax(0, 1fr);
    gap: 0 56px; align-items: start;
  }
}
/* Die erste Überschrift einer Spalte braucht keinen Abstand nach oben — der
   käme sonst zum Aussenabstand des Rasters dazu. */
.prog-col > .prog-h:first-child { margin-top: 18px; }
.prog-kicker { font-size: 12.5px; color: var(--muted); }
.prog-wrap h1 { margin-top: 5px; font-size: clamp(24px, 3vw, 34px); }
.prog-lead {
  margin-top: 7px; font-size: 13px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--accent);
}
.prog-sum { margin-top: 14px; font-size: 16px; line-height: 1.6; color: var(--ink-soft); }
.prog-h { margin-top: 26px; }
.prog-p { margin-top: 8px; font-size: 14.5px; line-height: 1.55; color: var(--ink-soft); }
.prog-list { margin: 8px 0 0; padding-left: 20px; }
.prog-list li { font-size: 14.5px; line-height: 1.5; margin-top: 4px; color: var(--ink-soft); }
.prog-list.minus { list-style: none; padding-left: 0; }
.prog-list.minus li { padding-left: 20px; position: relative; }
.prog-list.minus li::before {
  content: "–"; position: absolute; left: 4px; color: var(--muted);
}
/* Enger als vorher: 41 Pixel je Zeile machten aus einem Steckbrief mit
   zwanzig Zeilen einen Bildschirm. */
.prog-table { margin-top: 10px; border-collapse: collapse; width: 100%; }
.prog-table td {
  padding: 5px 0; border-top: 1px solid var(--line); font-size: 13.5px;
  line-height: 1.45; vertical-align: top;
}
.prog-table td:first-child { width: 52%; color: var(--muted); padding-right: 14px; }
/* Erklaervermerke: kleiner und blasser als der Fliesstext, aber OHNE
   Randlinie — die las sich wie ein Zitat, und zitiert wird hier niemand.
   Die Herkunft der Angaben steht ohnehin nicht mehr verstreut in solchen
   Vermerken, sondern in einem eigenen Abschnitt am Schluss der Seite. */
.prog-note { margin-top: 9px; font-size: 12px; line-height: 1.5; color: var(--muted); }
/* Geänderter Stand im Preisverlauf: die Zeile bekommt ein leises Merkmal,
   damit man nicht die Zahlen vergleichen muss, um die Änderung zu sehen. */
.hist-neu td { color: var(--ink); }
.hist-mark {
  margin-left: 8px; padding: 1px 7px; border-radius: 999px;
  font-size: 11px; letter-spacing: .03em; white-space: nowrap;
  border: 1px solid var(--line-hard); color: var(--muted);
}
/* Fundstellen: dieselben Pillen wie auf der Ergebnisseite der Suche */
.prog-cats { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 12px; }
.prog-cats a {
  padding: 4px 12px; border-radius: 999px; font-size: 13px;
  border: 1px solid var(--line-hard); text-decoration: none; color: var(--ink);
}
.prog-cats a:hover { border-color: var(--accent); color: var(--accent); }
.prog-cta { margin-top: 34px; }
/* Korrekturhinweis: leise, aber am Ort des Fundes */
.prog-fix { margin-top: 16px; font-size: 12.5px; color: var(--muted); }
.prog-fix a { color: var(--muted); }
.prog-fix a:hover { color: var(--accent); }

/* ------------------------------------------------------------ Marktbefund
   Kennzahlen über den Markt einer Kategorie. Die Gestaltung folgt genau einer
   Regel: die ZAHL ist die Aussage, alles andere ordnet sie ein. Deshalb steht
   sie gross und zuerst, die Beschriftung klein darunter, und der Satz, der sie
   einordnet, noch kleiner darunter.

   Keine Farben, keine Balken, keine Ampeln — die Seite soll aussehen wie eine
   Erhebung und nicht wie ein Dashboard. Wer sie zitiert, zitiert Zahlen; ein
   grüner Balken wäre eine Bewertung, und Bewertungen gibt es hier nicht. */
.mb-wrap { padding-top: 26px; padding-bottom: 48px; max-width: 1180px; }
.mb-wrap h1 { margin-top: 5px; font-size: clamp(24px, 3vw, 34px); }
/* Eigener Rahmen je Feld statt eines durchgehenden Rasters. Ein Raster aus
   Fugen sah gut aus, solange die Zahl der Felder durch die Spaltenzahl teilbar
   war — bei sieben Feldern in vier Spalten blieb ein grauer Rest stehen, der
   wie ein Fehler aussieht. Einzelne Karten dürfen eine Zeile unvollständig
   lassen. */
/* EINE Zeile mit allen Karten, sobald der Platz reicht. Sechs nebeneinander
   sind schmal — darum kleinere Zahl, engere Beschriftung und ein knapperer
   Einordnungssatz. Unter 1080 Pixeln bricht das Raster von selbst um; eine
   Zeile zu erzwingen hiesse dort, sechs unlesbare Spalten zu bauen. */
.mb-grid {
  margin-top: 26px; display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
@media (min-width: 1080px) {
  .mb-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
}
.mb-card {
  background: var(--surface); padding: 13px 13px 15px;
  border: 1px solid var(--line); border-radius: var(--r);
}
.mb-num {
  font-size: clamp(21px, 2.3vw, 27px); font-weight: 700; line-height: 1.1;
  letter-spacing: -.02em; font-variant-numeric: tabular-nums;
}
/* Zwei Zeilen Platz, ob die Beschriftung sie braucht oder nicht. «NENNEN
   EINEN PREIS» ist einzeilig, «MIT SCHWEIZER RECHTSTRÄGER» zweizeilig — ohne
   festes Mass begann der Erklärsatz in jeder Karte auf anderer Höhe, und die
   Reihe sah unruhig aus, obwohl die Karten gleich hoch sind. */
.mb-lab {
  margin-top: 4px; font-size: 11px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--muted); font-weight: 600; line-height: 1.35;
  min-height: 2.7em;
}
.mb-say { margin-top: 6px; font-size: 12.5px; line-height: 1.45; color: var(--ink-soft); }
.mb-h { margin-top: 34px; }
.mb-list { margin: 0; padding-left: 20px; }
.mb-list li { font-size: 14px; line-height: 1.55; margin-top: 4px; color: var(--ink-soft); }
/* Der Quellensatz zum Kopieren: als Fläche mit fester Schrift, damit erkennbar
   ist, dass er wörtlich übernommen werden soll und nicht gelesen. */
.mb-cite {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px; line-height: 1.65; color: var(--ink-soft);
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--r); padding: 12px 14px; user-select: all;
  /* Die Adresse am Schluss ist ein Wort ohne Trennstelle und breiter als ein
     Telefon. Ohne diese Zeile schob sie die ganze Seite nach rechts. */
  overflow-wrap: anywhere;
}
/* Preisgrafik: eine Zeile je Anbieter, Name links, Spur in der Mitte, Wert
   rechts. Kein SVG und kein Skript — die Grafik ist eine Liste mit einer
   Position je Zeile, und genau das kann CSS besser als eine Zeichenfläche:
   sie erbt die Schrift, bricht mit der Spalte um und lässt sich markieren.

   Das Spaltenraster steht EINMAL als Variable. Zeile, Medianstrich und Achse
   benutzen dasselbe — sonst zeigt die Marke «60» neben den Punkt, der 60
   kostet, und das fällt niemandem auf, der die Zahlen nicht nachrechnet. */
.pp {
  --pp-cols: minmax(96px, 200px) 1fr auto;
  --pp-gap: 12px;
  margin-top: 20px;
  /* MUSS positioniert sein. Die ausgeblendeten Kästchen liegen absolut; ohne
     positionierten Vorfahren ist ihr Bezug der Anfangsblock — und damit
     entkommen sie dem overflow:hidden des body, verlängern das Dokument und
     legen einen ZWEITEN Rollbalken über den der Seite. Die Seite wirkt dann
     verschoben, oben abgeschnitten und unten leer. */
  position: relative;
}
.pp-plot { position: relative; padding-top: 20px; }
.pp-row, .pp-med, .pp-axis {
  display: grid; grid-template-columns: var(--pp-cols); column-gap: var(--pp-gap);
}
.pp-row { align-items: center; padding: 3px 0; }
.pp-name {
  font-size: 13px; color: var(--ink-soft); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.pp-track {
  position: relative; height: 15px;
  background: linear-gradient(var(--line), var(--line)) left center / 100% 1px no-repeat;
}
.pp-dot {
  position: absolute; top: 50%; width: 9px; height: 9px; border-radius: 50%;
  background: var(--ink); transform: translate(-50%, -50%);
}
/* Fünf Musterbetriebe, fünf Töne von hell nach dunkel. Eine REIHENFOLGE und
   keine Palette: hell ist der kleinste Betrieb, dunkel der grösste. Damit
   liest man ohne Nachschlagen, ob der Preis mit dem Betrieb wächst — bei
   fünf bunten Farben müsste man jedes Mal die Legende befragen.

   Der helle Rand trennt Punkte, die dicht beieinanderliegen; ohne ihn
   verschmelzen zwei Nachbarn zu einem Fleck. */
.pp-mf .pp-dot {
  width: 10px; height: 10px; box-shadow: 0 0 0 1.5px var(--surface);
}
.pp-mf .pp-d0 { background: var(--ton-0); }
.pp-mf .pp-d1 { background: var(--ton-1); }
.pp-mf .pp-d2 { background: var(--ton-2); }
.pp-mf .pp-d3 { background: var(--ton-3); }
.pp-mf .pp-d4 { background: var(--ton-4); }

/* Der Filter. Die Kästchen selbst bleiben unsichtbar — bedient wird über die
   Beschriftung, und der Zustand kommt aus :checked. Kein Skript: ohne
   JavaScript steht hier ein Formular, das genau das tut, was draufsteht. */
.pp-cb { position: absolute; opacity: 0; pointer-events: none; }
.pp-bar { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }
.pp-chip {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  padding: 4px 11px 4px 9px; border-radius: 999px; font-size: 12.5px;
  border: 1px solid var(--line-hard); color: var(--ink-soft);
  user-select: none; transition: opacity .12s, border-color .12s;
}
.pp-chip:hover { border-color: var(--accent); }
.pp-sw { width: 10px; height: 10px; border-radius: 50%; flex: none; }
/* Abgewählt: blass und durchgestrichen, damit der Unterschied auch ohne Farbe
   erkennbar bleibt. */
.pp-c0:not(:checked) ~ .pp-bar .pp-l0,
.pp-c1:not(:checked) ~ .pp-bar .pp-l1,
.pp-c2:not(:checked) ~ .pp-bar .pp-l2,
.pp-c3:not(:checked) ~ .pp-bar .pp-l3,
.pp-c4:not(:checked) ~ .pp-bar .pp-l4 {
  opacity: .45; text-decoration: line-through; border-style: dashed;
}
.pp-c0:not(:checked) ~ .pp-plot .pp-d0,
.pp-c1:not(:checked) ~ .pp-plot .pp-d1,
.pp-c2:not(:checked) ~ .pp-plot .pp-d2,
.pp-c3:not(:checked) ~ .pp-plot .pp-d3,
.pp-c4:not(:checked) ~ .pp-plot .pp-d4 { display: none; }
/* Tastaturbedienung: das Kästchen ist ausgeblendet, der Fokusring muss darum
   auf der Beschriftung erscheinen. */
.pp-c0:focus-visible ~ .pp-bar .pp-l0,
.pp-c1:focus-visible ~ .pp-bar .pp-l1,
.pp-c2:focus-visible ~ .pp-bar .pp-l2,
.pp-c3:focus-visible ~ .pp-bar .pp-l3,
.pp-c4:focus-visible ~ .pp-bar .pp-l4 { outline: 2px solid var(--accent); outline-offset: 2px; }
.pp-val {
  font-size: 12.5px; color: var(--ink); font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Der Median geht durch ALLE Zeilen. Er ist die Aussage der Grafik; stünde er
   nur am Rand, müsste man ihn suchen. Die Beschriftung liegt OBEN — unten
   stünde sie im Namen der letzten Zeile. */
.pp-med { position: absolute; inset: 0; pointer-events: none; }
.pp-med > span { grid-column: 2; position: relative; }
.pp-med > span > i {
  position: absolute; top: 14px; bottom: 0; border-left: 1px dashed var(--accent);
}
.pp-med > span > i > b {
  position: absolute; top: -15px; left: 0; transform: translateX(-50%);
  font-size: 11px; font-weight: 600; color: var(--accent); white-space: nowrap;
}
.pp-axis > span { grid-column: 2; position: relative; height: 30px; }
.pp-axis > span > i {
  position: absolute; top: 15px; transform: translateX(-50%);
  font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums;
  font-style: normal; white-space: nowrap;
}
/* Die Schweigenden. Sie stehen als Fliesstext und nicht als leere Zeilen im
   Diagramm: sechzehn Zeilen ohne Punkt wären eine Wand, ein Satz mit sechzehn
   Namen ist eine Aussage. */
.pp-none {
  margin-top: 12px; font-size: 12.5px; line-height: 1.55; color: var(--muted);
}
.pp-none b { color: var(--ink-soft); }
/* Eng: Name über die Spur legen, sonst bleibt für die Spur nichts übrig. Das
   Raster wechselt auf zwei Spalten — die Spur bekommt die volle Breite, und
   Medianstrich wie Achse folgen automatisch, weil sie dieselbe Variable lesen. */
@media (max-width: 560px) {
  .pp { --pp-cols: 1fr auto; }
  .pp-row { grid-template-areas: "name wert" "spur spur"; padding: 5px 0; }
  .pp-name { grid-area: name; }
  .pp-val { grid-area: wert; }
  .pp-track { grid-area: spur; margin-top: 3px; }
  .pp-med > span, .pp-axis > span { grid-column: 1 / -1; }
}

/* Preisentwicklung. Alle Linien grau, eine fett — das ist der ganze Trick bei
   vierzehn Reihen: bunt wären sie ein Knäuel, in dem man vierzehnmal dieselbe
   Farbe sucht. Wer eine bestimmte Linie sucht, fährt sie an; sie tritt hervor
   und nennt ihren Namen im Hinweis des Browsers. */
/* Positioniert aus demselben Grund wie .pp: die ausgeblendeten Auswahlfelder
   dürfen nicht am Anfangsblock hängen. */
.vl { margin-top: 18px; position: relative; }
/* Auswahl der Ansicht. Alle Ansichten stehen im Quelltext, sichtbar ist die
   gewählte — reines CSS über :checked, kein Skript. Einfachauswahl und nicht
   Mehrfach wie beim Punktdiagramm: fünf Fälle mal vierzehn Anbieter wären
   siebzig Linien, und die Übersicht zeigt die fünf ohnehin als Median. */
.vl-rd { position: absolute; opacity: 0; pointer-events: none; }
.vl-bar { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 4px; }
.vl-chip {
  cursor: pointer; padding: 4px 12px; border-radius: 999px; font-size: 12.5px;
  border: 1px solid var(--line-hard); color: var(--muted); user-select: none;
}
.vl-chip:hover { border-color: var(--accent); color: var(--ink-soft); }
.vl-view { display: none; }
.vl-r0:checked ~ .vl-stack .vl-vw0,
.vl-r1:checked ~ .vl-stack .vl-vw1,
.vl-r2:checked ~ .vl-stack .vl-vw2,
.vl-r3:checked ~ .vl-stack .vl-vw3,
.vl-r4:checked ~ .vl-stack .vl-vw4,
.vl-r5:checked ~ .vl-stack .vl-vw5 { display: block; }
.vl-r0:checked ~ .vl-bar .vl-cl0,
.vl-r1:checked ~ .vl-bar .vl-cl1,
.vl-r2:checked ~ .vl-bar .vl-cl2,
.vl-r3:checked ~ .vl-bar .vl-cl3,
.vl-r4:checked ~ .vl-bar .vl-cl4,
.vl-r5:checked ~ .vl-bar .vl-cl5 {
  background: var(--ink); border-color: var(--ink); color: var(--bg);
}
/* Das Kästchen ist ausgeblendet, der Fokusring muss darum auf der
   Beschriftung erscheinen. */
.vl-r0:focus-visible ~ .vl-bar .vl-cl0,
.vl-r1:focus-visible ~ .vl-bar .vl-cl1,
.vl-r2:focus-visible ~ .vl-bar .vl-cl2,
.vl-r3:focus-visible ~ .vl-bar .vl-cl3,
.vl-r4:focus-visible ~ .vl-bar .vl-cl4,
.vl-r5:focus-visible ~ .vl-bar .vl-cl5 { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Die fünf Musterbetriebe in der Übersicht: dieselben Töne wie im
   Punktdiagramm darüber, hell nach dunkel mit der Betriebsgrösse. Eine
   Reihenfolge und keine Palette — hier trägt sie fünf Linien statt fünf
   Punktsorten, und die Zuordnung bleibt dieselbe. */
/* Doppelte Klasse, damit der Ton den Akzent der Medianlinie schlaegt: beide
   sind einfache Klassen, und ohne diesen Kniff gewaenne schlicht die spaetere
   Regel — fuenf Faelle in derselben Farbe. */
.vl-med.vl-t0, .vl-swm.vl-t0 { stroke: var(--ton-0); }
.vl-med.vl-t1, .vl-swm.vl-t1 { stroke: var(--ton-1); }
.vl-med.vl-t2, .vl-swm.vl-t2 { stroke: var(--ton-2); }
.vl-med.vl-t3, .vl-swm.vl-t3 { stroke: var(--ton-3); }
.vl-med.vl-t4, .vl-swm.vl-t4 { stroke: var(--ton-4); }
.vl-g:hover .vl-med.vl-t0, .vl-g:hover .vl-med.vl-t1, .vl-g:hover .vl-med.vl-t2,
.vl-g:hover .vl-med.vl-t3, .vl-g:hover .vl-med.vl-t4 { stroke-width: 4.5; }
.vl svg { display: block; width: 100%; height: auto; overflow: visible; }
.vl-grid { stroke: var(--line); stroke-width: 1; }
.vl-line {
  fill: none; stroke: var(--line-hard); stroke-width: 1.4;
  stroke-linejoin: round; transition: stroke .12s, stroke-width .12s;
}
/* Die ganze Gruppe ist das Ziel, nicht der 1,4 Pixel schmale Strich: sonst
   trifft man ihn nur mit ruhiger Hand. */
.vl svg g { cursor: default; }
.vl svg g:hover .vl-line { stroke: var(--ink); stroke-width: 2.4; }
.vl-med {
  fill: none; stroke: var(--accent); stroke-width: 2.6;
  stroke-linejoin: round; pointer-events: none;
}
/* Legende unter der Grafik. Legendeneintrag und Linie liegen in DERSELBEN
   Gruppe im SVG — darum reicht eine einzige Regel, um beim Ueberfahren beides
   hervorzuheben, und es braucht kein Skript. Eine Legende als HTML daneben
   koennte das nicht ohne eine eigene Regel je Eintrag. */
.vl-hit { fill: transparent; }
.vl-sw { stroke: var(--line-hard); stroke-width: 2; }
.vl-swm { stroke: var(--accent); stroke-width: 3; }
.vl-kn { fill: var(--ink-soft); font-size: 11px; dominant-baseline: middle; }
.vl-kv { fill: var(--muted); font-variant-numeric: tabular-nums; }
.vl-gm .vl-kn { fill: var(--accent); font-weight: 600; }
.vl-gm .vl-kv { fill: var(--accent); }
.vl-g:hover .vl-kn, .vl-g:hover .vl-kv { fill: var(--ink); font-weight: 600; }
.vl-g:hover .vl-sw { stroke: var(--ink); stroke-width: 3; }
.vl-ty { fill: var(--muted); font-size: 11px; text-anchor: end; dominant-baseline: middle; }
.vl-tx { fill: var(--muted); font-size: 11px; text-anchor: middle; }
/* Der Vermerk zu angenommenen Werten steht ÜBER der Grafik und nicht darunter.
   Unter ihr läse man ihn, nachdem man die Zahlen schon geglaubt hat. */
.vl-sim {
  margin-top: 8px; font-size: 12.5px; color: var(--accent);
  border: 1px dashed var(--accent); border-radius: var(--r); padding: 7px 11px;
  display: inline-block;
}

/* Die Flächen für ECharts. Sie tragen ihre Höhe aus dem Skript — vorher steht
   hier nichts, und die Seite springt beim Zeichnen nicht.

   Als Karte wie die Kennzahlen darüber: dieselbe helle Fläche, derselbe
   Rahmen, derselbe Radius. Eine Grafik, die direkt auf dem Seitengrund liegt,
   wirkt neben sieben Karten wie ein Fremdkörper — und der Punkt einer Karte
   ist hier derselbe wie dort: sie fasst zusammen, was zusammengehört. */
.ec {
  width: 100%; margin-top: 6px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r); padding: 14px 14px 10px;
}
.ec-bar { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 2px; }
.ec-chip {
  cursor: pointer; padding: 4px 12px; border-radius: 999px; font-size: 12.5px;
  border: 1px solid var(--line-hard); background: none; color: var(--muted);
  font-family: inherit; line-height: 1.5;
}
.ec-chip:hover { border-color: var(--accent); color: var(--ink-soft); }
.ec-chip[aria-pressed="true"] {
  background: var(--ink); border-color: var(--ink); color: var(--bg);
}

/* Balkenreihen der Dachseite. HTML mit Breitenangabe in Prozent, keine
   Zeichenbibliothek: es sind sechzehn Zeilen mit je einem Wert, und dafür
   647 KB nachzuladen wäre nicht zu rechtfertigen. Steht damit auch ohne
   Skript da.

   Der Anteil steht als ZAHL neben dem Balken, und dahinter die beiden Werte,
   aus denen er entsteht. Ein Balken allein lässt sich nicht zitieren. */
.bk { margin-top: 14px; }
.bk-row {
  display: grid; grid-template-columns: minmax(120px, 240px) 1fr auto;
  align-items: center; gap: 0 14px; padding: 4px 0;
}
.bk-name {
  font-size: 13px; color: var(--ink-soft); text-decoration: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  border-bottom: 1px dashed transparent;
}
.bk-name:hover { color: var(--accent); border-bottom-color: var(--accent); }
.bk-track {
  height: 13px; background: var(--surface-2); border: 1px solid var(--line);
  border-radius: 3px; overflow: hidden; display: block;
}
.bk-track i { display: block; height: 100%; background: var(--ink-soft); }
.bk-val {
  font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.bk-val b { color: var(--ink-soft); font-weight: 600; margin-left: 6px; }
/* Die schweizerischen Anforderungen sind keine Quote — sie hängen an der
   Kategorie und nicht an der Zahl ihrer Anbieter. Darum Gruppen mit Namen
   statt Balken. */
.bk-grps { margin-top: 12px; }
.bk-grp {
  font-size: 13px; line-height: 1.55; color: var(--muted); margin-top: 8px;
  padding-left: 12px; border-left: 2px solid var(--line);
}
.bk-grp b { color: var(--ink-soft); }
@media (max-width: 560px) {
  .bk-row { grid-template-columns: 1fr auto; }
  .bk-name { grid-column: 1; }
  .bk-val { grid-column: 2; }
  .bk-track { grid-column: 1 / -1; margin-top: 2px; }
}

/* Kacheln zu den ausführlichen Befunden. Sie sind das Ziel der Dachseite —
   als Verweiszeile gingen sie zwischen Balken und Zitierkasten unter. Grösser
   und luftiger als die Kennzahlenkarten oben, damit man sie nicht für eine
   fünfte Zahl hält: die tragen Werte, diese führen weiter. */
.mkz-lead { max-width: 78ch; }
.mkz {
  margin-top: 14px; display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
.mkz-card {
  position: relative; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r); padding: 16px 18px 15px;
  transition: border-color .12s, box-shadow .12s;
}
.mkz-card:hover { border-color: var(--accent); box-shadow: var(--shadow); }
.mkz-card h3 { font-size: 17px; line-height: 1.25; }
/* Der ganze Rahmen ist anklickbar: der Verweis spannt sich darüber. Dasselbe
   Muster wie bei den Kacheln der Startseite. */
.mkz-lnk { color: var(--ink); text-decoration: none; }
.mkz-lnk::after { content: ""; position: absolute; inset: 0; }
.mkz-card:hover .mkz-lnk { color: var(--accent); }
.mkz-was {
  margin-top: 7px; font-size: 13px; line-height: 1.5; color: var(--ink-soft);
}
.mkz-meta {
  margin-top: 10px; font-size: 12px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.mb-dl { margin-top: 16px; font-size: 13.5px; }
/* Der Absprung von der Kategorieseite. Er steht unter der Tabelle neben der
   Legende und trägt eine echte Zahl statt eines Etiketts — «Marktbefund» allein
   sagt niemandem, was ihn erwartet, «12 von 30 nennen einen Preis» schon. */
.mb-jump { padding: 10px 2px 0; font-size: 12.5px; }
.mb-jump a { color: var(--muted); text-decoration: none; border-bottom: 1px dashed var(--line-hard); }
.mb-jump a:hover { color: var(--accent); border-bottom-color: var(--accent); }

/* Querverweis auf die Gegenkategorie, über der Tabelle. «Buchhaltung» und
   «Buchhaltung auslagern» beantworten dieselbe Frage auf zwei Wege; wer auf der
   einen Seite landet, soll den anderen sehen, ohne ihn zu suchen.

   Als Fläche und nicht als Textzeile, damit es beim Überfliegen auffällt — aber
   in --good und nicht in --accent: Rot heisst auf dieser Seite «von Ihnen
   gewählt» und in der Schweiz-Leiste «zwingende Anforderung». Ein Wegweiser ist
   keines von beidem.

   LEISTE über die ganze Breite, oben bündig an die Bedienzeile. Kein Rahmen
   oben und keine runden Ecken: sie schliesst an, sie liegt nicht darauf. Nur
   unten eine Linie, damit sie sich von der Tabelle trennt.

   position: sticky mit left: 0 aus demselben Grund wie bei der Fusszeile —
   scrollt eine breite Vergleichstabelle waagrecht, bleibt die Leiste stehen
   statt mitzuwandern. */
.querband {
  display: block; margin: 0; padding: 0;
  background: color-mix(in srgb, var(--good) 9%, var(--surface));
  border-bottom: 1px solid color-mix(in srgb, var(--good) 32%, transparent);
  font-size: 13.5px; color: var(--ink-soft);
  /* sticky mit left: 0 aus demselben Grund wie bei der Fusszeile — scrollt eine
     breite Vergleichstabelle waagrecht, bleibt die Leiste an ihrem Platz.
     relative wäre für das X auch genug, sticky löst beides. */
  position: sticky; left: 0;
}
/* Der Text folgt dem seitlichen Einzug der Seite, die Fläche nicht. Rechts
   Platz für das X, damit ein langer Satz nicht darunter läuft. */
.querband-in {
  display: block; padding: 11px 20px;
  max-width: var(--maxw); margin: 0 auto;
}
.querband-in a { text-decoration: none; color: inherit; }
.querband-in a:hover { color: var(--ink); }
.querband-in a:hover .querband-ar { transform: translateX(2px); }
.querband b { color: var(--ink); font-weight: 640; }
.querband-ar { color: var(--good); font-weight: 700; display: inline-block; transition: transform .14s; }

/* Schliessknopf. Setzt app.js, darum steht er ohne JavaScript nicht da.
   Absolut rechts und über die ganze Höhe klickbar — ein 12px-Kreuz zu treffen
   ist unnötig fummelig, wenn daneben Platz ist. */
.querband-x {
  position: absolute; top: 0; right: 0; bottom: 0; width: 44px;
  border: 0; background: none; cursor: pointer; padding: 0;
  color: var(--muted); font-size: 17px; line-height: 1;
}
.querband-x:hover { color: var(--ink); }
.querband[hidden] { display: none; }
@media (max-width: 760px) { .querband-in { padding: 10px 14px; } }

/* .cat-note stand hier: eine Notiz über der Tabelle, die bei «Buchhaltung
   auslagern» den Referenzfall trug. Entfernt — über der Tabelle steht höchstens
   EIN Streifen, und das ist der Querverweis. Drei Streifen hatten das
   Leitprinzip «Bedienzeile, Tabelle, Fusszeile» wieder aufgeweicht. */

/* Kriterienseite: Überschrift und Erklärabsatz ÜBER der Tabelle. Auf der
   Kategorieseite steht dort bewusst nichts — der Vergleich ist die Seite. Hier
   ist der Text der Grund, warum die Seite mehr ist als ein gefilterter Ausschnitt,
   darum darf er Platz haben. Schmal gehalten: ein Absatz über die ganze Breite
   einer Vergleichstabelle wäre nicht mehr lesbar. */
/* Die Breitenschranke sitzt am TEXTBLOCK, nicht am ganzen Kopf: der Kopf ist
   seit dem 29. Juli 2026 eine Zeile mit dem Schweiz-Wähler rechts aussen, und
   78ch am Kopf hätten den Wähler mitgeschnürt statt den Lesetext. */
.crit-head { padding-top: 22px; padding-bottom: 18px; }
.crit-head .cat-head-t { max-width: 78ch; }
.crit-head h1 { font-size: clamp(21px, 2.4vw, 28px); letter-spacing: -.02em; }
.crit-head .cat-sub { margin-top: 11px; }
.crit-lead { margin: 10px 0 0; font-size: 15px; line-height: 1.6; color: var(--ink-soft); }
.crit-lead b { color: var(--ink); font-weight: 620; }
.crit-back { margin: 0; font-size: 13px; }
.crit-back a { color: var(--muted); }
.crit-back a:hover { color: var(--accent); }

.soon-wrap { padding-top: 26px; padding-bottom: 40px; max-width: 92ch; }
.soon-wrap h1 { font-size: clamp(22px, 2.6vw, 32px); }
.soon-lead { margin-top: 8px; font-size: 15px; color: var(--ink-soft); }
.soon-note {
  margin-top: 16px; padding: 12px 14px; font-size: 13.5px; line-height: 1.55;
  color: var(--ink-soft); background: var(--surface-2);
  border: 1px dashed var(--line-hard); border-radius: var(--r-s);
}
.soon-crit { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 0; }
.soon-crit li { padding: 12px 0 13px; border-top: 1px solid var(--line); }
.soon-crit b { display: block; font-size: 14px; font-weight: 620; }
.soon-crit span {
  display: block; margin-top: 4px; font-size: 13.5px; line-height: 1.55;
  color: var(--ink-soft);
}

.cat-demo {
  flex: none; padding: 1px 6px; border-radius: 3px; font-size: 9.5px;
  font-weight: 650; letter-spacing: .05em; text-transform: uppercase;
  background: var(--muted); color: var(--bg); white-space: nowrap;
}
.cat-card-demo { border-style: dashed; }

/* ------------------------------------------------------------- Impressum
   Reine Textseite. Die Offenlegung steht hier im Volltext; auf den
   Vergleichsseiten bleibt die Kurzform in der Kopfzeile. */
.legal .wrap { padding-top: 34px; padding-bottom: 44px; max-width: 760px; }
.legal h1 { font-size: clamp(26px, 3.2vw, 38px); }
.legal-lead { margin-top: 10px; font-size: 16px; color: var(--ink-soft); }
.legal .sub { margin-top: 30px; font-size: 16px; }
.legal-p { margin-top: 9px; font-size: 15px; color: var(--ink-soft); }
.legal-strong {
  padding: 13px 15px; background: var(--surface); border-radius: var(--r-s);
  border-left: 3px solid var(--accent); color: var(--ink);
}
.legal-list { margin: 9px 0 0; padding-left: 20px; font-size: 15px; color: var(--ink-soft); }
.legal-list li { margin-top: 5px; }

/* Betreiberangaben: Beschriftung und Wert nebeneinander, damit Firma, UID und
   MWST-Nummer auf einen Blick prüfbar sind. Auf schmalen Schirmen untereinander
   — eine zweispaltige Liste mit umbrechenden Nummern liest sich schlechter als
   zwei Zeilen. */
.legal-dl { margin: 12px 0 0; font-size: 15px; }
.legal-row {
  display: grid; grid-template-columns: 200px 1fr; gap: 4px 18px;
  padding: 7px 0; border-bottom: 1px solid var(--line);
}
.legal-row:first-child { border-top: 1px solid var(--line); }
.legal-row dt { color: var(--muted); font-size: 13.5px; padding-top: 1px; }
.legal-row dd { margin: 0; color: var(--ink); }
@media (max-width: 560px) {
  .legal-row { grid-template-columns: 1fr; gap: 2px; }
}

/* Offene Pflichtangaben — sichtbar unfertig, damit es nicht vergessen wird */
.legal-todo {
  margin-top: 12px; padding: 14px 16px;
  background: color-mix(in srgb, var(--accent) 7%, var(--surface));
  border: 1px dashed color-mix(in srgb, var(--accent) 40%, var(--line));
  border-radius: var(--r-s);
}
.legal-todo-h { font-size: 13px; font-weight: 650; color: var(--accent);
                text-transform: uppercase; letter-spacing: .06em; }
.legal-todo-note { margin-top: 6px; font-size: 13.5px; color: var(--ink-soft); }
.legal-todo ul { margin: 8px 0 0; padding-left: 20px; font-size: 14px; color: var(--ink-soft); }
.legal-todo li { margin-top: 3px; }

/* Die erste Ueberschrift folgt auf den Schluessel und braucht weniger — dort
   trennt schon dessen Rahmen. */
.start-sub:first-of-type { margin-top: 24px; }

/* ------------------------------------------- Seitenleiste «Entscheidet in der
   Schweiz». Weiter ein <details> und keine Skript-Lösung: so öffnet die Leiste
   auch ohne JavaScript und die Kriterien stehen im Quelltext. Panel und
   Abdunklung sind fixiert, liegen also nicht im Fluss — .chside braucht darum
   selbst keine Grösse. Rechts aussen in der Steuerleiste, weil es keine
   Filtereinstellung ist, sondern Auskunft über die Kategorie.

   Die Wähler tragen ZWEI Klassen (.chside > .chside-btn), weil das Element auch
   .dd trägt und .dd > summary sonst mit höherer Spezifität gewinnt — daran ist
   der erste Versuch gescheitert. */
/* Der EINZIGE automatische Rand in dieser Zeile — er schiebt den Wähler an den
   rechten Rand und setzt ihn damit sichtbar von den Filtern ab. Das ist richtig
   so: er filtert nicht, er erklärt.

   Trägt ein zweites Element derselben Zeile ebenfalls margin-left: auto, teilen
   sich beide den freien Platz und das erste steht in der Mitte statt am Rand.
   Genau daran lag es, als die Anzahl mitten in der Leiste hing. */
.chside { position: relative; flex: none; margin-left: auto; }
.chside > .chside-btn {
  list-style: none; cursor: pointer; user-select: none;
  display: inline-flex; align-items: center; gap: 8px; white-space: nowrap;
  padding: 5px 12px 5px 9px; border-radius: 999px;
  font-size: 12.5px; font-weight: 600;
  /* Neutral, nicht rot: Rot ist in der Steuerleiste für «noch nicht gesetzt»
     reserviert. Das Schweizerkreuz trägt die Farbe, der Knopf nicht. */
  color: var(--ink); background: var(--surface);
  border: 1px solid var(--line-hard);
  transition: background-color .14s, border-color .14s;
}
.chside > .chside-btn::-webkit-details-marker { display: none; }
/* Kein Chevron: die Leiste klappt nicht auf, sie fährt ein */
.chside > .chside-btn::after { content: none; }
.chside > .chside-btn:hover { background: var(--bg); border-color: var(--ink); }

/* Schweizerkreuz, dieselbe Geometrie wie die Marke im Kopf, auf 14px skaliert */
.ch-mark {
  width: 14px; height: 14px; border-radius: 2px; position: relative; flex: none;
  background: var(--ch-red);
}
.ch-mark span { position: absolute; background: #fff; border-radius: 1px; }
.ch-mark span:nth-child(1) { left: 5.9px; top: 2.6px; width: 2.2px; height: 8.8px; }
.ch-mark span:nth-child(2) { left: 2.6px; top: 5.9px; width: 8.8px; height: 2.2px; }

.chside-scrim {
  position: fixed; inset: 0; z-index: 55;
  background: rgba(10, 12, 14, .45); backdrop-filter: blur(2px);
}
.chside-panel {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 60;
  width: min(430px, 100%); overflow-y: auto;
  padding: 28px 28px 44px;
  background: var(--ch-red); color: #fff;
  box-shadow: var(--shadow-lg);
  animation: chslide .2s ease-out;
}
@keyframes chslide {
  from { transform: translateX(16px); opacity: .5; }
  to   { transform: none; opacity: 1; }
}
.chside-close {
  position: absolute; top: 14px; right: 16px;
  width: 32px; height: 32px; border-radius: 50%; cursor: pointer;
  border: 1px solid rgba(255, 255, 255, .35); background: none;
  color: #fff; font-size: 22px; line-height: 1;
  display: grid; place-items: center;
  transition: background-color .14s, border-color .14s;
}
.chside-close:hover { background: rgba(255, 255, 255, .16); border-color: #fff; }

/* Kicker mit umgekehrtem Kreuz: weisses Feld, rotes Kreuz — ein rotes Feld auf
   rotem Grund wäre unsichtbar. */
.chside-kicker {
  display: flex; align-items: center; gap: 8px;
  font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  font-weight: 650; color: rgba(255, 255, 255, .82);
}
.chside-kicker .ch-mark { background: #fff; }
.chside-kicker .ch-mark span { background: var(--ch-red); }
.chside-panel h2 { margin-top: 11px; font-size: 22px; color: #fff; }
.chside-lead {
  margin-top: 11px; font-size: 13.5px; line-height: 1.6;
  color: rgba(255, 255, 255, .93);
}
.chside-list { list-style: none; margin: 22px 0 0; padding: 0; display: grid; gap: 0; }
.chside-list li { padding: 13px 0 14px; border-top: 1px solid rgba(255, 255, 255, .3); }
.chside-list b { display: block; font-size: 14.5px; font-weight: 650; }
.chside-list span {
  display: block; margin-top: 4px; font-size: 13px; line-height: 1.55;
  color: rgba(255, 255, 255, .86);
}
/* Die Punkte stehen in EINER Liste. Bis Juli 2026 trennte hier eine
   Zwischenüberschrift die Kriterien der Kategorie von den allgemeinen Punkten —
   eine Einteilung, die nur intern existiert: für den Leser ist «Datenstandort»
   keine andere Sorte Anforderung als «TARDOC». */

/* ---------------------------------------------- Belege auf der Produktseite
   Die Quellenliste ist bewusst schlicht: sie soll benutzbar sein, nicht
   schmücken. Die Adressen stehen ausgeschrieben statt hinter einem Wort wie
   «Quelle», damit man vor dem Klick sieht, wohin es geht. */

/* Kategorien ohne schweizerische Kriterien sagen das über der Tabelle.
   Neutral gehalten, nicht als Warnung: es ist eine Einordnung, kein Mangel. */
.no-edge {
  margin: 0; padding: 9px 20px; font-size: 12.5px; color: var(--ink-soft);
  background: var(--surface-2); border-bottom: 1px solid var(--line);
}
.no-edge b { color: var(--ink); font-weight: 620; }

/* Preisquote je Kategorie. Balken UND Zahl: der Balken macht das Gefälle
   auf einen Blick sichtbar, die Zahl macht es nachprüfbar. Ohne Zahl wäre es
   Grafik, ohne Balken eine Liste, die niemand liest. */
.meth-kat { width: 100%; border-collapse: collapse; margin-top: 12px; font-size: 14px; }
.meth-kat td { padding: 5px 0; border-bottom: 1px solid var(--line); vertical-align: middle; }
.meth-kat td:first-child { color: var(--ink-soft); }
.meth-kat-n { width: 64px; text-align: right; padding-right: 14px !important;
  font-variant-numeric: tabular-nums; color: var(--muted); font-size: 13px; }
.meth-kat-bar { width: 40%; }
.meth-kat-bar i {
  display: block; height: 5px; border-radius: 2px; min-width: 2px;
  background: color-mix(in srgb, var(--ink) 55%, var(--surface));
}

/* Korrekturprotokoll. Der Leerzustand ist zurückhaltend gesetzt — er ist
   keine Entschuldigung, sondern eine Angabe. */
.meth-korr-leer { color: var(--muted); font-size: 14px; }
.korr-von { color: var(--muted); font-size: 12.5px; }


/* ZURUECKSETZEN AM FUSS DES FILTERPANELS. Als stiller Text und nicht als
   Knopf: er raeumt eine Einstellung weg und soll nicht mit den Schaltern
   konkurrieren, die sie setzen. Abgesetzt durch eine Haarlinie, weil er zu
   keiner der Gruppen darueber gehoert, sondern zu allen. */
.fp-reset { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line); }
.fp-reset button {
  border: 0; background: none; padding: 0; cursor: pointer;
  font: inherit; font-size: 11.5px; color: var(--accent);
  text-decoration: underline; text-underline-offset: 3px;
}
.fp-reset button:hover { color: var(--ink); }
