Zum Inhalt
← Hero & Section-Layouts

Preis-Sektion mit Haltung

Preise als typografische Tabelle auf Haarlinien statt drei Karten mit Badge: große Ziffern, die beim Wechsel der Zahlweise wie eine Walze rollen, Umfang zum Aufklappen.

Statt drei gleich hoher Karten mit „Beliebt“-Badge stehen die Preise als typografische Tabelle auf Rasterlinien: Paket, Umfang, Preis. Die Ziffern sind groß, die Linien fein. Ein Umschalter (monatlich oder jährlich, Einmalzahlung oder Raten, Lizenztyp) lässt die Ziffern als Walze oder Flip wechseln. Der Leistungsumfang ist aufklappbar statt einer endlosen Häkchenliste. Varianten: eine einzelne große Karte mit einem Preis, ein Lizenzrechner, eine Vergleichstabelle mit klebender Kopfzeile.

Klarheit statt Verkaufstrick. Eine typografische Tabelle wirkt hochwertig, ist gut lesbar und lässt sich ehrlich vergleichen.

Wann einsetzen

Design-Abos, Produkte, Kurse, Schrift-Foundries und Freelancer mit Paketpreisen.

Wann nicht

Wenn Preise individuell sind (dann ein Ab-Preis mit CTA) oder bei mehr als vier Paketen.

Varianten

  • Typografische Preistabelle
  • Ein-Preis-Karte für ein Abo
  • Lizenz- oder Preisrechner
  • Vergleichstabelle mit klebender Kopfzeile
  • Ziffern-Walze oder Flip beim Umschalten

Umsetzung

Eine echte <table> oder ein Grid mit subgrid, damit die Zeilen über alle Pakete ausgerichtet bleiben. Der Umschalter ist eine radio-Gruppe in fieldset und legend; den Preiswechsel kann CSS allein über :has(input:checked) steuern. Den Umfang in <details>/<summary> packen; neuere Chromes animieren ::details-content. Die klebende Kopfzeile bekommt ihren Schatten über scroll-state(stuck: top).

Robust umsetzen

Telefon. Pakete untereinander oder als waagerechte Scroll-Snap-Leiste. In der Vergleichstabelle bleibt die erste Spalte fest.

Ohne Bewegung. Preiswechsel ohne Ziffernanimation.

Barrierefrei. Preise als Text mit Währung und Zeitraum, auch wenn die Walze sie optisch zeigt. Umschalter mit sichtbarem Label. Tabellen mit <th scope> und <caption>.

Code

<!DOCTYPE html>
<!--
  Preis-Sektion mit Haltung
  Fundus · Hero & Section-Layouts · https://cecodesign.de/lab/fundus/hero-layouts/preis-section-editorial/
  Eigenständige Datei. Bilder liegen hier unter bilder/ – eigene einsetzen.
-->
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Preis-Sektion mit Haltung · Fundus-Demo</title>
<script>document.documentElement.classList.add('js');if(matchMedia('(prefers-reduced-motion: reduce)').matches)document.documentElement.classList.add('reduziert')</script>
<style>
@font-face { font-family: 'Bricolage Grotesque'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/bricolage-grotesque:vf@latest/latin-opsz-normal.woff2') format('woff2-variations'); font-weight: 200 800; font-display: swap; }
@font-face { font-family: 'Onest'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/onest:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-display: swap; }
@font-face { font-family: 'Shantell Sans'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/shantell-sans:vf@latest/latin-full-normal.woff2') format('woff2-variations'); font-weight: 300 800; font-display: swap; }
@font-face { font-family: 'Geist Mono'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/geist-mono:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-display: swap; }

:root {
  /* Rezept kurs-coaching-lernweg */
  --grund: #f4f6fa; --karte: #ffffff; --text: #141414; --leise: #5b6070; --linie: rgba(20, 20, 20, .13);
  --marke: #7491ff; --erfolg: #10bf7a; --hervor: #f7c325;
  --kurve: cubic-bezier(0.33, 1, 0.68, 1);   /* power2.out, Ziffernwechsel und UI */
  --ziffer-dauer: 400ms; --ziffer-stagger: 40ms; --ui: 300ms;
  --rand: clamp(18px, 4vw, 56px);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--grund); color: var(--text); font: 400 1.0625rem/1.6 'Onest', system-ui, sans-serif; -webkit-font-smoothing: antialiased; interpolate-size: allow-keywords; }
body { overflow-x: clip; }
a { color: inherit; }
button { font: inherit; color: inherit; }
:focus-visible { outline: 2px solid var(--marke); outline-offset: 3px; }
.sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.notiz { font-family: 'Shantell Sans', 'Onest', sans-serif; font-variation-settings: 'INFM' 45, 'BNCE' 0; font-weight: 450; font-size: .9375rem; line-height: 1.35; }
.headline { font-family: 'Bricolage Grotesque', sans-serif; font-optical-sizing: auto; }

/* Fundus-Leiste der Demo (nicht Teil der Kursseite) */
.demo-leiste { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 8px 20px; padding: 10px var(--rand); background: #141414; color: rgba(244, 246, 250, .62); font: 500 11px/1.4 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; }
.demo-leiste__modi { display: flex; gap: 4px; flex-wrap: wrap; }
.demo-leiste button { font: inherit; letter-spacing: inherit; text-transform: inherit; color: inherit; background: none; border: 1px solid rgba(244, 246, 250, .2); border-radius: 999px; height: 28px; padding: 0 12px; cursor: pointer; transition: background-color 150ms var(--kurve), color 150ms var(--kurve); }
.demo-leiste button[aria-pressed='true'] { background: #f4f6fa; color: #141414; border-color: #f4f6fa; }

/* Kopf der Kursseite */
.kopf { display: flex; justify-content: space-between; align-items: center; gap: 20px; max-width: 1280px; margin: 0 auto; padding: 22px var(--rand); }
.logo { display: flex; align-items: center; gap: 10px; font: 700 1.25rem/1 'Bricolage Grotesque', sans-serif; letter-spacing: -.02em; text-decoration: none; }
.logo svg { width: 26px; height: 26px; }
.kopf nav { display: flex; gap: 26px; font-size: .9375rem; color: var(--leise); }
.kopf nav a { text-decoration: none; }
.kopf nav a[aria-current] { color: var(--text); font-weight: 500; }
.knopf { display: inline-flex; align-items: center; gap: .5em; height: 44px; padding: 0 18px; border: 0; border-radius: 999px; background: var(--marke); color: #141414; font-weight: 550; font-size: .9375rem; text-decoration: none; cursor: pointer; transition: background-color 150ms var(--kurve), transform 150ms var(--kurve); }
.knopf:hover { background: #8aa2ff; }
.knopf:active { transform: scale(.97); }
.knopf--leer { background: transparent; box-shadow: inset 0 0 0 1.5px #141414; }
.knopf--leer:hover { background: #141414; color: #fff; }

/* Sektion */
.preise { max-width: 1280px; margin: 0 auto; padding: clamp(36px, 7vh, 88px) var(--rand) clamp(56px, 10vh, 120px); }
.preise__kopf { display: grid; grid-template-columns: 7fr 1fr 4fr; gap: 24px; align-items: end; }
.preise__kopf .notiz { color: #3d4ad1; display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.preise__kopf .notiz svg { width: 34px; height: 18px; }
.preise h2 { font: 700 clamp(2.25rem, 1.4rem + 3.4vw, 4.25rem)/1.05 'Bricolage Grotesque', sans-serif; font-variation-settings: 'opsz' 96; letter-spacing: -.02em; text-wrap: balance; }
.marker { position: relative; white-space: nowrap; }
.marker svg { position: absolute; left: -2%; bottom: -.04em; width: 104%; height: .42em; z-index: -1; }
.preise__lead { grid-column: 3; max-width: 40ch; color: #2c2f38; text-wrap: pretty; }
.start { white-space: nowrap; display: inline-flex; align-items: center; gap: 8px; margin-top: 14px; padding: 5px 12px 5px 6px; border-radius: 999px; background: #fff; font-size: .875rem; font-weight: 500; box-shadow: 0 0 0 1px var(--linie); }
.start b { white-space: nowrap; background: var(--hervor); border-radius: 999px; padding: 2px 9px; font-weight: 600; }

.steuer { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 14px 24px; margin: clamp(28px, 5vh, 52px) 0 18px; }
.zahlweise { border: 0; display: flex; align-items: center; gap: 14px; }
.zahlweise legend { float: left; font-size: .875rem; color: var(--leise); margin-right: 4px; }
.schalter { position: relative; display: inline-grid; grid-auto-flow: column; grid-auto-columns: 1fr; padding: 4px; border-radius: 999px; background: #fff; box-shadow: 0 0 0 1px var(--linie); }
.schalter input { position: absolute; opacity: 0; pointer-events: none; }
.schalter label { position: relative; z-index: 1; padding: 0 18px; height: 38px; display: grid; place-items: center; font-size: .9375rem; font-weight: 500; white-space: nowrap; cursor: pointer; transition: color var(--ui) var(--kurve); }
.schalter input:checked + label { color: #fff; }
.schalter input:focus-visible + label { outline: 2px solid var(--marke); outline-offset: 2px; border-radius: 999px; }
.schalter .gleiter { position: absolute; z-index: 0; top: 4px; bottom: 4px; left: 4px; width: calc((100% - 8px) / 2); border-radius: 999px; background: #141414; transition: translate var(--ui) var(--kurve); }
.schalter:has(input:nth-of-type(2):checked) .gleiter { translate: 100% 0; }
.steuer__hinweis { font-size: .875rem; color: var(--leise); }
.steuer__hinweis b { color: var(--text); font-weight: 550; }

/* Ziffernwalze */
.preis { display: inline-flex; align-items: baseline; gap: .08em; font: 700 clamp(2.5rem, 1.6rem + 2.6vw, 4.25rem)/1 'Bricolage Grotesque', sans-serif; font-variation-settings: 'opsz' 96; letter-spacing: -.03em; font-variant-numeric: tabular-nums; }
.preis .waehrung { font-size: .42em; font-weight: 600; letter-spacing: 0; align-self: flex-start; margin-top: .16em; margin-left: .07em; }
.walze { display: inline-flex; height: 1.05em; overflow: hidden; }
.slot { display: block; width: var(--ziffer, .62em); height: 1.05em; overflow: hidden; transition: width var(--ziffer-dauer) var(--kurve); transition-delay: var(--d, 0ms); }
.slot.punkt { width: .26em; }
.slot.leer { width: 0; }
.slot span { display: block; line-height: 1.05em; height: 1.05em; text-align: center; }
.streifen { display: block; transition: translate var(--ziffer-dauer) var(--kurve); transition-delay: var(--d, 0ms); }
.zeitraum { display: block; font-size: .875rem; color: var(--leise); margin-top: 8px; min-height: 1.4em; }
.zeitraum span { transition: opacity var(--ui) var(--kurve); }

/* Variante A: typografische Tabelle */
.tabelle { width: 100%; border-collapse: collapse; border-top: 2px solid #141414; }
.tabelle thead th { font: 500 .8125rem/1 'Onest', sans-serif; color: var(--leise); text-align: left; padding: 14px 0; border-bottom: 1px solid var(--linie); }
.tabelle th[scope='row'], .tabelle td { vertical-align: top; padding: 30px 24px 30px 0; border-bottom: 1px solid var(--linie); }
.tabelle tbody tr { position: relative; }
.tabelle .paket { width: 30%; text-align: left; font-weight: 400; }
.tabelle .umfang { width: 38%; }
.tabelle .preisspalte { width: 20%; }
.tabelle .aktion { width: 12%; text-align: right; padding-right: 0; }
.paket h3 { font: 650 1.75rem/1.1 'Bricolage Grotesque', sans-serif; font-variation-settings: 'opsz' 48; letter-spacing: -.02em; margin-bottom: 8px; }
.paket p { font-size: .9375rem; color: #3a3e49; max-width: 30ch; }
.paket .notiz { display: inline-flex; gap: 6px; align-items: flex-start; margin-top: 12px; color: #3d4ad1; rotate: -1.5deg; max-width: 28ch; }
.paket .notiz svg { flex: none; width: 22px; height: 22px; margin-top: 1px; }
.umfang ul { list-style: none; display: grid; gap: 6px; }
.umfang li { display: grid; grid-template-columns: 20px 1fr; gap: 8px; font-size: 1rem; }
.umfang li svg { width: 16px; height: 16px; margin-top: 5px; color: var(--erfolg); }
.umfang li.plus { color: var(--leise); }
details { margin-top: 14px; }
summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 8px; font-size: .9375rem; font-weight: 550; text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 4px; }
summary::-webkit-details-marker { display: none; }
summary i { display: inline-grid; place-items: center; width: 20px; height: 20px; border-radius: 50%; box-shadow: inset 0 0 0 1.5px currentColor; font-style: normal; font-size: 14px; line-height: 1; transition: rotate var(--ui) var(--kurve); }
details[open] summary i { rotate: 45deg; }
details::details-content { block-size: 0; overflow: clip; transition: block-size var(--ui) var(--kurve), content-visibility var(--ui) allow-discrete; }
details[open]::details-content { block-size: auto; }
.module { list-style: none; counter-reset: m; display: grid; gap: 10px; padding: 14px 0 4px; }
.module li { display: grid; grid-template-columns: auto 1fr; gap: 2px 12px; font-size: .9375rem; color: #2c2f38; }
.module li .notiz { grid-row: span 2; font-size: .8125rem; color: #3d4ad1; padding-top: 2px; white-space: nowrap; }
.module li b { font-weight: 550; color: var(--text); }
.tabelle tbody tr:hover > * { background: linear-gradient(#fff, #fff) no-repeat; }
.fussnote { display: flex; flex-wrap: wrap; gap: 6px 22px; margin-top: 20px; font-size: .875rem; color: var(--leise); }
.fussnote span::before { content: ''; display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--erfolg); margin-right: 8px; vertical-align: .15em; }

/* Variante B: ein Preis */
.einpreis { display: grid; grid-template-columns: 5fr 6fr; gap: clamp(24px, 4vw, 64px); background: var(--karte); border-radius: 22px; padding: clamp(24px, 4vw, 56px); box-shadow: 0 1px 0 var(--linie), 0 30px 60px -40px rgba(20, 20, 40, .25); }
.einpreis h3 { font: 650 1.75rem/1.1 'Bricolage Grotesque', sans-serif; letter-spacing: -.02em; margin: 8px 0 18px; }
.einpreis .preis { font-size: clamp(4rem, 2.6rem + 5vw, 7.5rem); }
.einpreis .knopf { margin-top: 26px; }
.einpreis .umfang ul { gap: 12px; }
.einpreis .notiz { color: #3d4ad1; }
.einpreis__rechts { border-left: 1px solid var(--linie); padding-left: clamp(20px, 3vw, 48px); }

/* Variante C: Vergleich mit klebender Kopfzeile */
.vergleich-rahmen { max-height: min(560px, 70vh); overflow: auto; border-top: 2px solid #141414; overscroll-behavior: contain; }
.vergleich { width: 100%; border-collapse: separate; border-spacing: 0; }
.vergleich thead th { position: sticky; top: 0; z-index: 2; text-align: left; vertical-align: bottom; padding: 0; border-bottom: 1px solid var(--linie); }
.vergleich thead .kz { padding: 18px 16px 16px 0; min-height: 100%; background: var(--grund); transition: box-shadow var(--ui) var(--kurve), background-color var(--ui) var(--kurve); }
@supports (container-type: scroll-state) {
  .vergleich thead th { container-type: scroll-state; }
  @container scroll-state(stuck: top) { .kz { background: #fff; box-shadow: 0 10px 20px -16px rgba(20, 20, 40, .45); } }
}
.vergleich thead th:first-child { font: 500 .8125rem/1.3 'Onest', sans-serif; color: var(--leise); }
.vergleich thead h3 { font: 650 1.25rem/1.1 'Bricolage Grotesque', sans-serif; letter-spacing: -.02em; }
.vergleich thead .preis { font-size: 2rem; margin-top: 6px; }
.vergleich thead .zeitraum { margin-top: 2px; font-size: .8125rem; }
.vergleich.klebt thead .kz { background: #fff; box-shadow: 0 10px 20px -16px rgba(20, 20, 40, .45); }
.vergleich tbody th, .vergleich tbody td { padding: 14px 16px 14px 0; border-bottom: 1px solid var(--linie); text-align: left; font-size: .9375rem; }
.vergleich tbody th { font-weight: 450; color: #2c2f38; width: 34%; }
.vergleich tbody td { width: 22%; }
.vergleich td svg { width: 18px; height: 18px; color: var(--erfolg); vertical-align: -3px; }
.vergleich td.nein { color: #a2a7b4; }
.vergleich .gruppe th { padding-top: 26px; font: 650 .8125rem/1 'Onest', sans-serif; letter-spacing: .02em; color: var(--text); }

[data-ansicht] { display: none; }
[data-ansicht].an { display: block; animation: rein var(--ui) var(--kurve); }
@keyframes rein { from { opacity: 0; translate: 0 8px; } }

.vorschau body { overflow: hidden; }
.vorschau .demo-leiste, .vorschau .kopf, .vorschau .preise__lead, .vorschau .fussnote { display: none; }
.vorschau .preise { padding-top: 26px; }
.vorschau .preise__kopf { grid-template-columns: 1fr; }
.vorschau .steuer { margin-top: 22px; }
.vorschau .tabelle th[scope='row'], .vorschau .tabelle td { padding-top: 22px; padding-bottom: 22px; }
.vorschau .vergleich-rahmen { max-height: 470px; }

@media (max-width: 960px) {
  .kopf nav { display: none; }
  .preise__kopf { grid-template-columns: 1fr; }
  .preise__lead { grid-column: 1; }
  .einpreis { grid-template-columns: 1fr; }
  .einpreis__rechts { border-left: 0; padding-left: 0; border-top: 1px solid var(--linie); padding-top: 24px; }
}
@media (max-width: 720px) {
  .kopf .knopf { display: none; }
  .tabelle thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .tabelle, .tabelle tbody, .tabelle tr, .tabelle th[scope='row'], .tabelle td { display: block; width: auto !important; }
  .tabelle tbody tr { display: grid; grid-template-columns: 1fr auto; padding: 24px 0; border-bottom: 1px solid var(--linie); }
  .tabelle th[scope='row'], .tabelle td { padding: 0; border: 0; }
  .tabelle .paket { grid-column: 1 / -1; }
  .tabelle .preisspalte { grid-row: 2; grid-column: 1; margin-top: 16px; }
  .tabelle .umfang { grid-column: 1 / -1; grid-row: 3; margin-top: 16px; }
  .tabelle .aktion { grid-row: 2; grid-column: 2; align-self: center; margin-top: 16px; }
  .paket .notiz { margin-top: 10px; }
  .zahlweise legend { display: none; }
  .vergleich-rahmen { max-height: 70vh; }
  .vergleich { min-width: 620px; }
  .vergleich tbody th, .vergleich thead th:first-child { position: sticky; left: 0; z-index: 1; background: var(--grund); }
  .vergleich thead th:first-child .kz { padding-right: 12px; }
  .vergleich thead th:first-child { z-index: 3; }
  .vergleich tbody th { width: 150px; min-width: 150px; padding-right: 12px; }
}
@media (max-width: 400px) { .start { flex-wrap: wrap; row-gap: 4px; border-radius: 16px; padding-block: 6px; } .start span { white-space: nowrap; padding-left: 3px; } }
@media (hover: none) { .tabelle tbody tr:hover > * { background: none; } .knopf { height: 48px; } }

:root.reduziert .slot, :root.reduziert .streifen, :root.reduziert .gleiter, :root.reduziert .zeitraum span, :root.reduziert details::details-content, :root.reduziert summary i { transition: none !important; }
:root.reduziert [data-ansicht].an { animation: none; }
</style>
</head>
<body>
<div class="demo-leiste">
  <span>Preis-Sektion · Variante</span>
  <div class="demo-leiste__modi" role="group" aria-label="Variante der Demo">
    <button type="button" data-variante="tabelle" aria-pressed="true">Preistabelle</button>
    <button type="button" data-variante="einpreis" aria-pressed="false">Ein Preis</button>
    <button type="button" data-variante="vergleich" aria-pressed="false">Vergleich</button>
  </div>
  <span data-werte>power2.out · 400 ms · 40 ms je Ziffer · tabular-nums</span>
</div>

<header class="kopf">
  <a class="logo" href="#"><svg viewBox="0 0 26 26" aria-hidden="true"><path d="M3 12.5 13 4l10 8.5V23H3z" fill="#7491ff"/><path d="M9 23v-6.5h8V23" fill="none" stroke="#141414" stroke-width="2"/><path d="M3 12.5 13 4l10 8.5" fill="none" stroke="#141414" stroke-width="2" stroke-linejoin="round"/></svg>Altbauschule</a>
  <nav aria-label="Abschnitte"><a href="#">Lernweg</a><a href="#">Kursleitung</a><a href="#">Stimmen</a><a href="#" aria-current="true">Preise</a><a href="#">Fragen</a></nav>
  <a class="knopf" href="#">Platz sichern</a>
</header>

<main class="preise" aria-labelledby="preise-titel">
  <div class="preise__kopf">
    <div>
      <p class="notiz"><svg viewBox="0 0 34 18" aria-hidden="true"><path d="M2 9c8-5 18-6 28 0m0 0-5-5m5 5-6 4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>Preise &amp; Pakete</p>
      <h2 id="preise-titel" class="headline" data-titel>Ein Kurs, <span class="marker">drei Wege</span> hindurch.</h2>
    </div>
    <div class="preise__lead">
      <p data-lead>Alle drei enthalten denselben Kurs: sechs Module, 14 Stunden Video, Vorlagen für deinen Sanierungsfahrplan. Der Unterschied ist nur, wie viel Begleitung du willst.</p>
      <p class="start" data-start><b>Nächster Start</b> <span>12. Januar · 6 Wochen · online</span></p>
    </div>
  </div>

  <div class="steuer">
    <fieldset class="zahlweise">
      <legend data-legende>Zahlweise</legend>
      <div class="schalter">
        <input type="radio" name="zahlweise" id="z1" value="a" checked><label for="z1" data-l1>Einmal zahlen</label>
        <input type="radio" name="zahlweise" id="z2" value="b"><label for="z2" data-l2>In 3 Raten</label>
        <span class="gleiter" aria-hidden="true"></span>
      </div>
    </fieldset>
    <p class="steuer__hinweis" data-hinweis><b>Raten ohne Aufpreis.</b> Die Summe bleibt gleich.</p>
  </div>

  <!-- Variante A -->
  <section data-ansicht="tabelle" class="an" aria-label="Pakete als Tabelle">
    <table class="tabelle">
      <caption class="sr">Drei Pakete des Kurses „Altbau verstehen“ mit Umfang und Preis</caption>
      <thead><tr><th scope="col">Paket</th><th scope="col">Umfang</th><th scope="col">Preis</th><th scope="col"><span class="sr">Auswahl</span></th></tr></thead>
      <tbody>
        <tr>
          <th scope="row" class="paket"><h3>Selbst lernen</h3><p>Du willst verstehen, was dein Haus braucht, und planst danach selbst.</p></th>
          <td class="umfang">
            <ul>
              <li><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2.5 8.5 6 12l7.5-8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>6 Module, 14 Std. Video</li>
              <li><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2.5 8.5 6 12l7.5-8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Vorlagen für den Sanierungsfahrplan</li>
              <li><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2.5 8.5 6 12l7.5-8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Forum und 12 Monate Zugang</li>
            </ul>
            <details>
              <summary><i aria-hidden="true">+</i>Alle sechs Module</summary>
              <ol class="module">
                <li><span class="notiz">Modul 01 · 40 min</span><b>Das Haus lesen</b><span>Baujahr, Konstruktion, typische Schwachstellen</span></li>
                <li><span class="notiz">Modul 02 · 55 min</span><b>Wärme und Feuchte</b><span>Wo Energie verloren geht und warum Wände schimmeln</span></li>
                <li><span class="notiz">Modul 03 · 45 min</span><b>Dach, Fenster, Keller</b><span>Reihenfolge, die später nichts kaputt macht</span></li>
                <li><span class="notiz">Modul 04–06</span><b>Heizung, Kosten, Fahrplan</b><span>Angebote vergleichen, Förderung finden, Plan schreiben</span></li>
              </ol>
            </details>
          </td>
          <td class="preisspalte"><span class="preis" data-preis="390|130"></span><span class="zeitraum" data-zeitraum="einmalig|pro Monat, 3 Monate"></span></td>
          <td class="aktion"><a class="knopf knopf--leer" href="#">Wählen</a></td>
        </tr>
        <tr>
          <th scope="row" class="paket"><h3>Mit Begleitung</h3><p>Du willst Rückfragen stellen und deinen Plan prüfen lassen.</p>
            <span class="notiz"><svg viewBox="0 0 22 22" aria-hidden="true"><path d="M3 15c3-7 9-11 16-11m0 0-4-1m4 1-2 4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>Reicht für die meisten Häuser.</span></th>
          <td class="umfang">
            <ul>
              <li class="plus"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2.5 8.5 6 12l7.5-8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Alles aus „Selbst lernen“</li>
              <li><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2.5 8.5 6 12l7.5-8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>3 Live-Sprechstunden, je 90 min</li>
              <li><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2.5 8.5 6 12l7.5-8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Plan-Check: schriftliches Feedback zu deinem Fahrplan</li>
            </ul>
            <details data-auf>
              <summary><i aria-hidden="true">+</i>So laufen die Sprechstunden</summary>
              <ol class="module">
                <li><span class="notiz">Woche 2</span><b>Fragen zu deinem Haus</b><span>Höchstens 12 Teilnehmende, Fotos und Pläne mitbringen</span></li>
                <li><span class="notiz">Woche 4</span><b>Angebote lesen</b><span>Wir gehen echte Handwerkerangebote gemeinsam durch</span></li>
                <li><span class="notiz">Woche 6</span><b>Plan-Check</b><span>Du schickst deinen Fahrplan, Ruth antwortet in 5 Werktagen</span></li>
              </ol>
            </details>
          </td>
          <td class="preisspalte"><span class="preis" data-preis="870|290"></span><span class="zeitraum" data-zeitraum="einmalig|pro Monat, 3 Monate"></span></td>
          <td class="aktion"><a class="knopf" href="#">Wählen</a></td>
        </tr>
        <tr>
          <th scope="row" class="paket"><h3>Mit Vor-Ort-Termin</h3><p>Dein Haus steht in Südbaden und jemand soll es sich selbst ansehen.</p></th>
          <td class="umfang">
            <ul>
              <li class="plus"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2.5 8.5 6 12l7.5-8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Alles aus „Mit Begleitung“</li>
              <li><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2.5 8.5 6 12l7.5-8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Begehung, 3 Std., bis 60 km um Freiburg</li>
              <li><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2.5 8.5 6 12l7.5-8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Wärmebilder von November bis März</li>
            </ul>
            <details>
              <summary><i aria-hidden="true">+</i>Was bei der Begehung passiert</summary>
              <ol class="module">
                <li><span class="notiz">vorher</span><b>Unterlagen sichten</b><span>Pläne, Rechnungen, Verbrauch der letzten drei Jahre</span></li>
                <li><span class="notiz">vor Ort</span><b>Rundgang vom Keller bis unters Dach</b><span>Feuchtemessung, Wärmebilder, Fotos für deinen Plan</span></li>
                <li><span class="notiz">danach</span><b>Protokoll in 10 Werktagen</b><span>Mit Reihenfolge und groben Kosten je Maßnahme</span></li>
              </ol>
            </details>
          </td>
          <td class="preisspalte"><span class="preis" data-preis="1470|490"></span><span class="zeitraum" data-zeitraum="einmalig|pro Monat, 3 Monate"></span></td>
          <td class="aktion"><a class="knopf knopf--leer" href="#">Wählen</a></td>
        </tr>
      </tbody>
    </table>
  </section>

  <!-- Variante B -->
  <section data-ansicht="einpreis" aria-label="Ein Preis als Abo">
    <div class="einpreis">
      <div>
        <p class="notiz">Abo für Kursteilnehmende</p>
        <h3>Sprechstunde im Abo</h3>
        <span class="preis" data-preis="29|290"></span><span class="zeitraum" data-zeitraum="pro Monat, monatlich kündbar|pro Jahr, entspricht 24,17 € im Monat"></span>
        <a class="knopf" href="#">Abo beginnen</a>
      </div>
      <div class="einpreis__rechts umfang">
        <ul>
          <li><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2.5 8.5 6 12l7.5-8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Jeden ersten Dienstag live, 19 bis 20:30 Uhr</li>
          <li><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2.5 8.5 6 12l7.5-8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Alle Kursinhalte, solange das Abo läuft</li>
          <li><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2.5 8.5 6 12l7.5-8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Neue Vorlagen, wenn sich Förderregeln ändern</li>
          <li><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2.5 8.5 6 12l7.5-8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>Aufzeichnung, falls du nicht kannst</li>
        </ul>
        <p class="notiz" style="margin-top:22px">Gedacht für alle, die gerade bauen und zwischendurch eine zweite Meinung brauchen.</p>
      </div>
    </div>
  </section>

  <!-- Variante C -->
  <section data-ansicht="vergleich" aria-label="Pakete im Vergleich">
    <div class="vergleich-rahmen" data-vergleich-rahmen tabindex="0" aria-label="Vergleichstabelle, scrollbar">
      <span data-waechter style="display:block;height:1px"></span>
      <table class="vergleich" data-vergleich>
        <caption class="sr">Leistungen der drei Pakete im Vergleich</caption>
        <thead><tr>
          <th scope="col"><div class="kz">Leistung</div></th>
          <th scope="col"><div class="kz"><h3>Selbst lernen</h3><span class="preis" data-preis="390|130"></span><span class="zeitraum" data-zeitraum="einmalig|× 3 Monate"></span></div></th>
          <th scope="col"><div class="kz"><h3>Mit Begleitung</h3><span class="preis" data-preis="870|290"></span><span class="zeitraum" data-zeitraum="einmalig|× 3 Monate"></span></div></th>
          <th scope="col"><div class="kz"><h3>Mit Vor-Ort-Termin</h3><span class="preis" data-preis="1470|490"></span><span class="zeitraum" data-zeitraum="einmalig|× 3 Monate"></span></div></th>
        </tr></thead>
        <tbody data-zeilen></tbody>
      </table>
    </div>
  </section>

  <p class="fussnote"><span>Alle Preise inkl. 19 % MwSt.</span><span>14 Tage Rückgaberecht ohne Begründung</span><span>Rechnung auf Wunsch an deinen Arbeitgeber</span></p>
</main>

<script>
(() => {
  const html = document.documentElement;
  const reduziert = () => html.classList.contains('reduziert');
  const $ = (s, r = document) => r.querySelector(s), $$ = (s, r = document) => [...r.querySelectorAll(s)];

  /* ---------- Vergleichszeilen ---------- */
  const JA = '<svg viewBox="0 0 18 18" aria-hidden="true"><path d="M3 9.5 7 13.5l8-9" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg><span class="sr">enthalten</span>';
  const NEIN = '<span aria-hidden="true">—</span><span class="sr">nicht enthalten</span>';
  const ZEILEN = [
    ['Kurs'],
    ['Videos, 6 Module, 14 Std.', 1, 1, 1], ['Vorlagen Sanierungsfahrplan', 1, 1, 1], ['Zugang', '12 Monate', '12 Monate', '24 Monate'], ['Forum', 1, 1, 1], ['Teilnahmebescheinigung', 1, 1, 1],
    ['Begleitung'],
    ['Live-Sprechstunden', 0, '3 × 90 min', '3 × 90 min'], ['Plan-Check, schriftlich', 0, 1, 1], ['Antwort auf Fragen im Forum', 'Gemeinschaft', 'Ruth, 2 Werktage', 'Ruth, 2 Werktage'],
    ['Vor Ort'],
    ['Begehung mit Ruth Messerschmid', 0, 0, '3 Std.'], ['Wärmebilder', 0, 0, 'Nov. bis März'], ['Protokoll mit Kostenrahmen', 0, 0, 1],
    ['Bedingungen'],
    ['Rückgaberecht', '14 Tage', '14 Tage', '14 Tage'], ['Raten ohne Aufpreis', 1, 1, 1],
  ];
  $('[data-zeilen]').innerHTML = ZEILEN.map(z => z.length === 1
    ? '<tr class="gruppe"><th scope="rowgroup" colspan="4">' + z[0] + '</th></tr>'
    : '<tr><th scope="row">' + z[0] + '</th>' + z.slice(1).map(w => w === 1 ? '<td>' + JA + '</td>' : w === 0 ? '<td class="nein">' + NEIN + '</td>' : '<td>' + w + '</td>').join('') + '</tr>').join('');

  /* ---------- Ziffernwalze ---------- */
  const fmt = n => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, '.');
  const idx = ch => ch === '.' ? 10 : ch === ' ' ? 11 : +ch;
  const STREIFEN = '<span>0</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span><span>6</span><span>7</span><span>8</span><span>9</span><span>.</span><span>&nbsp;</span>';
  const preise = $$('[data-preis]').map(el => {
    const werte = el.dataset.preis.split('|').map(fmt), len = Math.max(...werte.map(w => w.length));
    el.innerHTML = '<span class="walze" aria-hidden="true">' + Array.from({ length: len }, () => '<span class="slot"><span class="streifen">' + STREIFEN + '</span></span>').join('') + '</span><span class="waehrung" aria-hidden="true">€</span><span class="sr" data-sr></span>';
    const zr = el.nextElementSibling && el.nextElementSibling.dataset.zeitraum ? el.nextElementSibling : null;
    if (zr) zr.innerHTML = '<span></span>';
    return { el, werte, len, slots: $$('.slot', el), zr, zrTexte: zr ? zr.dataset.zeitraum.split('|') : [] };
  });
  function zeigePreis(p, i, sofort) {
    const text = p.werte[i].padStart(p.len, ' ');
    p.slots.forEach((s, k) => {
      const ch = text[k], d = sofort || reduziert() ? 0 : k * 40;
      s.style.setProperty('--d', d + 'ms');
      s.classList.toggle('punkt', ch === '.'); s.classList.toggle('leer', ch === ' ');
      s.firstChild.style.translate = '0 ' + (-idx(ch) * 1.05) + 'em';
    });
    p.el.querySelector('[data-sr]').textContent = p.werte[i].replace('.', '') + ' Euro, ' + (p.zrTexte[i] || '');
    if (p.zr) {
      const sp = p.zr.firstChild;
      if (sofort || reduziert()) { sp.textContent = p.zrTexte[i]; return; }
      sp.style.opacity = 0;
      setTimeout(() => { sp.textContent = p.zrTexte[i]; sp.style.opacity = 1; }, 160);
    }
  }
  function ziffernBreite() {
    const p = $('.preis'); if (!p) return;
    const m = document.createElement('span'); m.textContent = '0'; m.style.cssText = 'position:absolute;visibility:hidden;font:inherit;font-variant-numeric:tabular-nums';
    p.appendChild(m); const w = m.getBoundingClientRect().width / parseFloat(getComputedStyle(p).fontSize); m.remove();
    if (w > 0) html.style.setProperty('--ziffer', w.toFixed(3) + 'em');
  }

  /* ---------- Zahlweise ---------- */
  let stand = 0;
  const radios = $$('input[name="zahlweise"]');
  function setzeZahlweise(i, sofort) {
    stand = i; radios[i].checked = true;
    preise.forEach(p => zeigePreis(p, i, sofort));
  }
  radios.forEach((r, i) => r.addEventListener('change', () => { pause(); setzeZahlweise(i); }));

  /* ---------- Varianten ---------- */
  const MARKER = '<svg viewBox="0 0 200 20" preserveAspectRatio="none" aria-hidden="true"><path d="M3 13c40-7 120-9 194-4" fill="none" stroke="#f7c325" stroke-width="12" stroke-linecap="round"/></svg>';
  const KOPF = {
    paket: ['Ein Kurs, <span class="marker">drei Wege' + MARKER + '</span> hindurch.', 'Alle drei enthalten denselben Kurs: sechs Module, 14 Stunden Video, Vorlagen für deinen Sanierungsfahrplan. Der Unterschied ist nur, wie viel Begleitung du willst.', '<b>Nächster Start</b> <span>12. Januar · 6 Wochen · online</span>'],
    abo: ['Dranbleiben zum <span class="marker">festen Preis' + MARKER + '</span>.', 'Wer gerade saniert, hat jeden Monat neue Fragen. Die Sprechstunde gibt es deshalb auch ohne Paket, für alle, die den Kurs schon kennen.', '<b>Nächste Sprechstunde</b> <span>Di., 3. November · 19 Uhr</span>'],
  };
  const TEXTE = {
    tabelle: { l: ['Einmal zahlen', 'In 3 Raten'], legende: 'Zahlweise', hinweis: '<b>Raten ohne Aufpreis.</b> Die Summe bleibt gleich.' },
    vergleich: { l: ['Einmal zahlen', 'In 3 Raten'], legende: 'Zahlweise', hinweis: '<b>Kopfzeile klebt beim Scrollen</b> und zeigt die Preise weiter an.' },
    einpreis: { l: ['Monatlich', 'Jährlich'], legende: 'Abrechnung', hinweis: '<b>Ein Preis, kein Paketvergleich.</b> Monatlich kündbar.' },
  };
  let variante = 'tabelle';
  function setzeVariante(v) {
    variante = v;
    $$('[data-ansicht]').forEach(a => a.classList.toggle('an', a.dataset.ansicht === v));
    $$('[data-variante]').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.variante === v)));
    const t = TEXTE[v];
    const k = KOPF[v === 'einpreis' ? 'abo' : 'paket'];
    $('[data-titel]').innerHTML = k[0]; $('[data-lead]').textContent = k[1]; $('[data-start]').innerHTML = k[2];
    $('[data-l1]').textContent = t.l[0]; $('[data-l2]').textContent = t.l[1]; $('[data-legende]').textContent = t.legende; $('[data-hinweis]').innerHTML = t.hinweis;
    $('[data-werte]').textContent = v === 'vergleich' ? 'Sticky-Kopf · scroll-state(stuck: top) · Schatten 300 ms power2.out' : 'power2.out · 400 ms · 40 ms je Ziffer · tabular-nums';
    setzeZahlweise(0, true);
    const rahmen = $('[data-vergleich-rahmen]'); if (rahmen) rahmen.scrollTop = 0;
  }
  $$('[data-variante]').forEach(b => b.addEventListener('click', () => { pause(); setzeVariante(b.dataset.variante); }));

  /* ---------- Klebende Kopfzeile: scroll-state-Query, sonst Wächter-Element ---------- */
  const tabelle = $('[data-vergleich]'), rahmen = $('[data-vergleich-rahmen]');
  new IntersectionObserver(([e]) => tabelle.classList.toggle('klebt', !e.isIntersecting), { root: rahmen, threshold: 0 }).observe($('[data-waechter]'));

  /* ---------- Vorführung ---------- */
  let uhren = [], scrollUhr = null;
  const spaeter = (ms, fn) => uhren.push(setTimeout(fn, ms));
  function rahmenFahrt(ziel, dauer) {
    cancelAnimationFrame(scrollUhr);
    const start = rahmen.scrollTop, t0 = performance.now();
    const s = t => { const p = Math.min(1, (t - t0) / dauer), e = reduziert() ? 1 : 1 - Math.pow(1 - p, 2); rahmen.scrollTop = start + (ziel - start) * e; if (p < 1) scrollUhr = requestAnimationFrame(s); };
    scrollUhr = requestAnimationFrame(s);
  }
  function vorfuehren() {
    pause();
    html.classList.add('vorfuehrung');
    setzeVariante('tabelle');
    const auf = $('[data-auf]');
    auf.open = false;
    spaeter(900, () => setzeZahlweise(1));
    spaeter(2900, () => { auf.open = true; });
    spaeter(4600, () => setzeZahlweise(0));
    spaeter(6400, () => { auf.open = false; });
    spaeter(7600, () => setzeVariante('vergleich'));
    spaeter(8500, () => rahmenFahrt(260, 900));
    spaeter(9900, () => setzeZahlweise(1));
    spaeter(11600, () => rahmenFahrt(0, 700));
    spaeter(12600, () => setzeVariante('einpreis'));
    spaeter(13500, () => setzeZahlweise(1));
    spaeter(15300, () => setzeZahlweise(0));
    spaeter(16800, vorfuehren);
  }
  function pause() { uhren.forEach(clearTimeout); uhren = []; cancelAnimationFrame(scrollUhr); html.classList.remove('vorfuehrung'); }
  function neustart() { pause(); setzeVariante(variante); $$('details').forEach(d => { d.open = false; }); }
  function weiter() { vorfuehren(); }

  ziffernBreite();
  setzeVariante('tabelle');
  if (document.fonts) document.fonts.ready.then(ziffernBreite);

})();
</script>
</body>
</html>