Zum Inhalt
← Typo-Techniken

Tabellenziffern für Preis- und Datenblöcke

Kostenseite einer Konstanzer Physiopraxis: Preise, Sprechzeiten und ein Serienrechner mit Tabellenziffern, die Kommas stehen untereinander und die Summe zappelt nicht.

Tabellenziffern sind Ziffern mit gleicher Dickte: jede Ziffer nimmt gleich viel Breite ein. Mit font-variant-numeric: tabular-nums stehen Preise, Uhrzeiten und Kennzahlen sauber untereinander, und Zahlen, die sich ändern, verschieben den Text daneben nicht. Mono-Ziffern sind eine gestalterische Variante desselben Prinzips.

Gleich breite Ziffern machen Spalten vergleichbar: Komma unter Komma, Stelle unter Stelle. Das Auge liest Größenordnungen auf einen Blick. Bei Zählern, Summen und Uhren bleibt die Zeile ruhig, statt bei jeder Änderung zu springen. Das wirkt genau und vertrauenswürdig.

Wann einsetzen

Überall, wo Zahlen verglichen werden oder sich ändern: Preislisten, Tarife, Rechner, Warenkorb, Öffnungszeiten, Fahrpläne, Tabellen, Statistiken, Zähler, Countdowns und Diagramm-Legenden.

Wann nicht

In Fließtext und großen Überschriften wirken Tabellenziffern löchrig, weil die schmale 1 viel Luft um sich hat. Dort bleiben proportionale Ziffern. Schriften ohne tnum-Feature ignorieren die Angabe; dann hilft nur eine andere Schrift oder Mono-Ziffern.

Varianten

  • tabular-nums allein (Stripe, Zähler und Preise)
  • lining-nums tabular-nums, dazu slashed-zero für eine eindeutige Null (Linear, Diagramm-Legenden)
  • Mono-Ziffern als Gestaltungselement: Zahlen bewusst in einer Monospace-Schrift
  • Gegenprobe proportional-nums für Fließtext und Headlines

Umsetzung

Allen Zahlen eine Klasse geben und dort font-variant-numeric: lining-nums tabular-nums setzen, am besten über eine Custom Property, damit Zusätze wie slashed-zero nicht den ganzen Wert überschreiben. Preisspalten rechtsbündig mit gleich vielen Nachkommastellen setzen, dann stehen die Kommas untereinander. Vorher prüfen, ob die Schrift das Feature tnum hat (im Browser vergleichen oder mit wakamaifondue.com); font-feature-settings: 'tnum' nur als Rückfall.

Robust umsetzen

Telefon. Gilt unverändert. Auf schmalen Bildschirmen Spalten zusammenlegen, die Zahlenspalte aber rechtsbündig behalten.

Ohne Bewegung. Nicht relevant, die Technik hat keine Bewegung. Sie beruhigt sogar Zahlen, die sich ändern.

Barrierefrei. Bessere Lesbarkeit für Zahlenvergleiche. Zahlen als echten Text setzen, Tabellen mit th und scope auszeichnen. Ändernde Summen mit aria-live ansagen. Eine Null mit Strich hilft bei Verwechslung mit O.

Code

<!DOCTYPE html>
<!--
  Tabellenziffern für Preis- und Datenblöcke
  Fundus · Typo-Techniken · https://cecodesign.de/lab/fundus/typo-techniken/tabellenziffern/
  Eigenständige Datei. Bilder liegen hier unter bilder/ – eigene einsetzen.
-->
<html lang="de" data-ziffern="tab">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Tabellenziffern für Preis- und Datenblöcke · 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: 'Hedvig Letters Serif'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/hedvig-letters-serif:vf@latest/latin-opsz-normal.woff2') format('woff2-variations'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Google Sans Flex'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/google-sans-flex:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 100 1000; 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 praxis-gesundheit-ruhig */
  --grund: #f5f7f7; --flaeche: #e2e0de; --marke: #005450; --marke-tief: #00403d;
  --text: #363b3b; --leise: #596666; --linie: rgba(54, 59, 59, .14);
  --akzent: #ffc776; --hilfslinie: #d9822b;
  --kurve: cubic-bezier(0.2, 0, 0.38, 0.9);   /* Token tech-praezise */
  --ui: 150ms var(--kurve);
  --lage: 250ms var(--kurve);

  /* Die Technik: alle Zahlen hängen an einer Variable */
  --ziffern: lining-nums tabular-nums;
  --null: ;
}
:root[data-ziffern="prop"] { --ziffern: lining-nums proportional-nums; }
:root.nullstrich { --null: slashed-zero; }

* { box-sizing: border-box; margin: 0; padding: 0; }
[hidden] { display: none !important; }
html { background: var(--grund); color: var(--text); font-family: 'Google Sans Flex', system-ui, sans-serif; font-size: 100%; -webkit-font-smoothing: antialiased; }
body { min-height: 100svh; padding: clamp(14px, 2.4vw, 32px) clamp(16px, 3.4vw, 48px) 40px; }
.mono { font: 500 11px/1.3 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; }
button, input { font: inherit; color: inherit; }
:focus-visible { outline: 2px solid var(--marke); outline-offset: 3px; border-radius: 4px; }

/* Ziffern: eine Klasse, die Variable entscheidet */
.z { font-variant-numeric: var(--ziffern) var(--null); }
:root[data-ziffern="mono"] .z { font-family: 'Geist Mono', ui-monospace, monospace; font-variant-numeric: normal; letter-spacing: -.03em; font-size: .9em; font-weight: 450; }

/* ——— Demo-Leiste (im Fluss, nicht über dem Inhalt) ——— */
.leiste { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px 24px; padding-bottom: 18px; color: var(--leise); }
.leiste-links { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 20px; }
.umschalter { display: flex; gap: 2px; border: 1px solid var(--linie); border-radius: 999px; padding: 3px; background: #fff; }
.umschalter button { letter-spacing: inherit; text-transform: inherit; font-size: inherit; color: var(--leise); background: none; border: 0; border-radius: 999px; padding: 7px 12px; cursor: pointer; transition: color var(--ui), background-color var(--ui); }
.umschalter button:hover { color: var(--text); }
.umschalter button[aria-pressed="true"] { background: var(--marke); color: #fff; }
.schalter { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; }
.schalter input { width: 14px; height: 14px; accent-color: var(--marke); }
.leiste code { font: inherit; text-transform: none; letter-spacing: .02em; color: var(--text); }

/* ——— Seite: Loretto Physiotherapie ——— */
.seite { background: #fff; border-radius: 6px; box-shadow: 0 1px 0 rgba(0, 0, 0, .03), 0 24px 60px -36px rgba(0, 40, 38, .35); overflow: hidden; }
.kopf { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 18px clamp(18px, 3vw, 40px); border-bottom: 1px solid var(--linie); }
.marke { display: flex; align-items: center; gap: 12px; color: var(--marke); text-decoration: none; }
.marke svg { width: 34px; height: 34px; flex: none; }
.marke b { display: block; font: 400 1.5rem/1 'Hedvig Letters Serif', Georgia, serif; font-variation-settings: 'opsz' 24; letter-spacing: -.01em; }
.marke small { display: block; margin-top: 3px; font-size: .78rem; font-weight: 500; color: var(--leise); letter-spacing: .01em; }
.nav { display: flex; gap: clamp(14px, 2vw, 28px); font-size: 1rem; }
.nav a { color: var(--text); text-decoration: none; padding: 4px 0; border-bottom: 1.5px solid transparent; transition: border-color var(--ui), color var(--ui); }
.nav a:hover { color: var(--marke); }
.nav a[aria-current] { color: var(--marke); border-color: var(--marke); }
.kopf-rechts { display: flex; align-items: center; gap: 18px; }
.tel { color: var(--text); text-decoration: none; font-weight: 500; white-space: nowrap; }
.tel span { color: var(--leise); font-weight: 400; margin-right: 6px; }
.tel b { font-weight: 500; }
.knopf { display: inline-flex; align-items: center; gap: 8px; height: 44px; padding: 0 20px; border-radius: 999px; background: var(--marke); color: #fff; font-weight: 500; font-size: 1rem; text-decoration: none; border: 0; cursor: pointer; transition: background-color var(--ui); white-space: nowrap; }
.knopf:hover { background: var(--marke-tief); }

.kosten { display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(320px, 1fr); gap: clamp(24px, 4vw, 64px); padding: clamp(26px, 3.6vw, 48px) clamp(18px, 3vw, 40px) clamp(24px, 3vw, 40px); align-items: start; }
.augenbraue { font-size: .9375rem; font-weight: 500; color: var(--marke); display: flex; align-items: center; gap: 10px; }
.augenbraue::before { content: ""; width: 18px; height: 1.5px; background: currentColor; }
h1 { margin-top: 12px; font: 400 clamp(2.25rem, 1.4rem + 2.4vw, 3.5rem)/1.06 'Hedvig Letters Serif', Georgia, serif; font-variation-settings: 'opsz' 24; letter-spacing: -.015em; color: var(--marke-tief); text-wrap: balance; }
.lead { margin-top: 14px; font-size: 1.0625rem; line-height: 1.6; max-width: 56ch; color: var(--text); text-wrap: pretty; }

/* Preistabelle */
.tabelle-feld { position: relative; margin-top: 44px; }
.preise { width: 100%; border-collapse: collapse; font-size: 1.0625rem; }
.preise th { text-align: left; font-weight: 500; font-size: .875rem; color: var(--leise); padding: 0 0 10px; border-bottom: 1px solid var(--text); }
.preise th.r, .preise td.r { text-align: right; }
.preise td { padding: 11px 0; border-bottom: 1px solid var(--linie); vertical-align: baseline; }
.preise td:first-child { padding-right: 16px; }
.preise td b { font-weight: 500; }
.preise td small { display: block; font-size: .875rem; color: var(--leise); margin-top: 2px; }
.preise .d-mobil { display: none; white-space: nowrap; }
.preise .dauer { width: 6.5em; color: var(--leise); }
.preise .preis { width: 8.2em; font-weight: 500; font-size: 1.1875rem; color: var(--marke-tief); }
.preise .preis .eur { color: var(--leise); font-weight: 400; margin-left: .18em; }
.preise .preis .ab { font-size: .8125rem; color: var(--leise); font-weight: 400; margin-right: .3em; }
.fuss { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 6px 20px; margin-top: 12px; font-size: .875rem; color: var(--leise); }

/* Hilfslinien: wo stehen die Kommas? */
.hilfe { position: absolute; inset: 0; pointer-events: none; opacity: 1; transition: opacity var(--lage); }
.ohne-hilfe .hilfe { opacity: 0; }
.hilfe .achse { position: absolute; top: 26px; width: 0; border-left: 1px dashed var(--hilfslinie); opacity: .7; transition: translate var(--lage); }
.hilfe .marke-k { position: absolute; width: 2px; height: 9px; margin-left: -1px; background: var(--hilfslinie); border-radius: 1px; transition: translate var(--lage), background-color var(--lage); }
.hilfe .marke-k.weg { background: #c2412d; }
.modus { position: absolute; right: 0; top: -36px; display: inline-flex; align-items: center; gap: 8px; height: 24px; padding: 0 10px; border-radius: 999px; background: var(--grund); border: 1px solid var(--linie); color: var(--leise); text-transform: none; letter-spacing: .02em; }
.modus i { width: 7px; height: 7px; border-radius: 50%; background: var(--marke); transition: background-color var(--lage); }
:root[data-ziffern="prop"] .modus i { background: #c2412d; }
.modus b { font-weight: 500; color: var(--text); }

/* Rechner */
.rechner { position: relative; background: var(--marke); color: #eef5f3; border-radius: 6px; padding: clamp(22px, 2.6vw, 32px); }
.rechner h2 { font: 400 1.75rem/1.12 'Hedvig Letters Serif', Georgia, serif; font-variation-settings: 'opsz' 24; letter-spacing: -.01em; color: #fff; }
.rechner > p { margin-top: 8px; font-size: .9375rem; line-height: 1.5; color: rgba(238, 245, 243, .78); }
.wahl { margin-top: 20px; display: grid; gap: 6px; border: 0; }
.wahl legend, .zahl-feld .bez { font-size: .875rem; color: rgba(238, 245, 243, .72); margin-bottom: 6px; }
.wahl label { display: grid; grid-template-columns: 18px 1fr auto; align-items: center; gap: 10px; padding: 9px 12px; border-radius: 4px; background: rgba(255, 255, 255, .06); cursor: pointer; transition: background-color var(--ui); }
.wahl label:hover { background: rgba(255, 255, 255, .1); }
.wahl input { appearance: none; width: 16px; height: 16px; border-radius: 50%; border: 1.5px solid rgba(238, 245, 243, .6); display: grid; place-items: center; transition: border-color var(--ui); }
.wahl input::after { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--akzent); scale: 0; transition: scale var(--ui); }
.wahl input:checked { border-color: var(--akzent); }
.wahl input:checked::after { scale: 1; }
.wahl input:focus-visible { outline: 2px solid var(--akzent); outline-offset: 2px; }
.wahl label:has(input:checked) { background: rgba(255, 255, 255, .13); }
.wahl .z { color: #fff; font-weight: 500; }
.zeile2 { display: flex; flex-wrap: wrap; align-items: end; justify-content: space-between; gap: 14px; margin-top: 16px; }
.stepper { display: inline-flex; align-items: center; gap: 4px; background: rgba(255, 255, 255, .06); border-radius: 999px; padding: 4px; }
.stepper button { width: 36px; height: 36px; border-radius: 50%; border: 0; background: rgba(255, 255, 255, .1); color: #fff; font-size: 1.25rem; line-height: 1; cursor: pointer; transition: background-color var(--ui), scale var(--ui); }
.stepper button:hover, .stepper button.gedrueckt { background: rgba(255, 255, 255, .22); }
.stepper button.gedrueckt { scale: .92; }
.stepper button:focus-visible { outline-color: var(--akzent); }
.stepper output { min-width: 2.4em; text-align: center; font-size: 1.25rem; font-weight: 500; color: #fff; }
.fango { display: inline-flex; align-items: center; gap: 9px; font-size: .9375rem; cursor: pointer; padding-bottom: 8px; }
.fango input { appearance: none; flex: none; width: 18px; height: 18px; margin: 0; border-radius: 4px; border: 1.5px solid rgba(238, 245, 243, .6); display: grid; place-items: center; cursor: pointer; transition: border-color var(--ui), background-color var(--ui); }
.fango input::after { content: ""; width: 9px; height: 5px; margin-top: -3px; border: solid var(--marke-tief); border-width: 0 0 2px 2px; rotate: -45deg; scale: 0; transition: scale var(--ui); }
.fango input:checked { background: var(--akzent); border-color: var(--akzent); }
.fango input:checked::after { scale: 1; }
.fango input:focus-visible { outline: 2px solid var(--akzent); outline-offset: 2px; }
.posten { margin-top: 18px; border-top: 1px solid rgba(238, 245, 243, .18); padding-top: 12px; display: grid; gap: 6px; font-size: .9375rem; color: rgba(238, 245, 243, .8); }
.posten div { display: flex; justify-content: space-between; gap: 12px; }
.posten .z { color: #fff; }
.summe-feld { position: relative; margin-top: 14px; padding-top: 12px; border-top: 1px solid rgba(238, 245, 243, .18); display: flex; justify-content: space-between; align-items: end; gap: 10px; }
.summe-feld .bez { font-size: .9375rem; padding-bottom: 10px; }
.summe { position: relative; font-size: clamp(2.6rem, 1.8rem + 2.2vw, 3.6rem); line-height: 1; font-weight: 500; letter-spacing: -.02em; color: #fff; white-space: nowrap; }
.summe .eur { font-size: .5em; font-weight: 400; color: rgba(238, 245, 243, .7); margin-left: .12em; }
.breite { position: absolute; left: 0; right: 0; top: -10px; height: 7px; border: 1px solid var(--akzent); border-top: 0; opacity: .9; transition: opacity var(--lage); }
.ohne-hilfe .breite, .ohne-hilfe .breite-wert { opacity: 0; }
.breite-wert { display: block; margin-top: 10px; text-align: right; color: var(--akzent); font-size: 10.5px; letter-spacing: .1em; transition: opacity var(--lage); }
.rechner .knopf { margin-top: 18px; width: 100%; justify-content: center; background: #f5f7f7; color: var(--marke-tief); }
.rechner .knopf:hover { background: #fff; }
.rechner .hinweis { margin-top: 10px; font-size: .8125rem; line-height: 1.45; color: rgba(238, 245, 243, .74); }

/* Sprechzeiten */
.zeiten { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 18px clamp(18px, 3vw, 40px); padding: 22px clamp(18px, 3vw, 40px) 26px; background: var(--grund); border-top: 1px solid var(--linie); }
.zeiten div { font-size: .9375rem; line-height: 1.5; }
.zeiten dt { font-size: .8125rem; color: var(--leise); margin-bottom: 3px; }
.zeiten dd { font-weight: 500; color: var(--text); }
.zeiten dd + dd { font-weight: 400; color: var(--leise); }
.zeiten a { color: inherit; text-decoration-color: var(--linie); text-underline-offset: 3px; }

/* Werte unter der Bühne */
.werte { display: grid; grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); gap: 6px 32px; margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--linie); color: var(--leise); font: 400 11.5px/1.6 'Geist Mono', ui-monospace, monospace; }
.werte div { display: flex; gap: 12px; min-width: 0; }
.werte dt { color: var(--text); flex: none; min-width: 9em; }
.werte dd { overflow-wrap: anywhere; }

/* Schmalere Fenster */
@media (max-width: 1100px) {
  .nav { display: none; }
}
@media (max-width: 900px) {
  .kosten { grid-template-columns: 1fr; }
  .zeiten { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
  body { padding: 12px 10px 28px; }
  .leiste { gap: 10px; }
  .umschalter { width: 100%; }
  .umschalter button { flex: 1; padding: 8px 2px; letter-spacing: .04em; }
  .kopf { padding: 14px 16px; }
  .tel { display: none; }
  .kopf .knopf { height: 40px; padding: 0 14px; font-size: .9375rem; }
  .marke small { display: none; }
  .kosten { padding: 22px 16px; }
  .preise .dauer, .preise th.dauer-kopf { display: none; }
  .preise .d-mobil { display: inline; }
  .preise .preis { width: 6.6em; font-size: 1.0625rem; }
  .preise .ab { display: none; }
  .preise th { font-size: .8125rem; }
  .zeiten { grid-template-columns: 1fr 1fr; padding: 20px 16px; }
  .rechner { padding: 20px 16px; }
  .werte { grid-template-columns: minmax(0, 1fr); }
  .werte dt { min-width: 7em; }
}
/* Sehr schmal (320 px): Terminknopf zweizeilig statt Querscrollen */
@media (max-width: 360px) {
  .kopf { gap: 12px; }
  .kopf .knopf { white-space: normal; height: auto; min-height: 40px; padding: 6px 14px; line-height: 1.15; text-align: center; }
}
/* Kontrastmodus: Knöpfe mit Rahmen */
@media (forced-colors: active) { button, [role=button], [role=tab], [role=switch] { border: 1px solid ButtonText !important; } }

/* Vorschau 960 × 720: nur Tabelle und Rechner */
.vorschau body { overflow: hidden; padding: 18px 22px; }
.vorschau .leiste, .vorschau .werte, .vorschau .zeiten, .vorschau .lead, .vorschau .nav, .vorschau .tel, .vorschau .rechner > p, .vorschau .rechner .hinweis, .vorschau .rechner .knopf, .vorschau .fuss { display: none; }
.vorschau .kosten { grid-template-columns: minmax(0, 1.3fr) minmax(300px, 1fr); padding: 26px 28px 30px; gap: 36px; }
.vorschau .kosten > * { min-width: 0; }
.vorschau h1 { font-size: 2.125rem; }
.vorschau .tabelle-feld { margin-top: 54px; }
.vorschau .preise { font-size: 1rem; }
.vorschau .preise td { padding: 9px 0; }
.vorschau .preise .dauer { width: 4.6em; }
.vorschau .modus { top: -42px; height: 28px; padding: 0 12px; font-size: 12.5px; }
.vorschau .modus i { width: 8px; height: 8px; }

/* Reduzierte Bewegung: alles sofort */
:root.reduziert *, :root.reduziert *::before, :root.reduziert *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; }

/* Touch: größere Ziele, kein Hover-Zustand */
@media (hover: none) {
  .stepper button { width: 44px; height: 44px; }
  .wahl label { padding: 12px; }
  .umschalter button { padding-block: 10px; }
  .nav a:hover { color: var(--text); }
}
</style>
</head>
<body>
<div class="leiste mono">
  <div class="leiste-links">
    <div class="umschalter" role="group" aria-label="Ziffernart">
      <button type="button" data-modus="prop" aria-pressed="false">Proportional</button>
      <button type="button" data-modus="tab" aria-pressed="true">Tabellenziffern</button>
      <button type="button" data-modus="mono" aria-pressed="false">Mono-Ziffern</button>
    </div>
    <label class="schalter"><input type="checkbox" data-null> Null mit Strich</label>
    <label class="schalter"><input type="checkbox" data-hilfe checked> Hilfslinien</label>
  </div>
  <span><code data-css>font-variant-numeric: lining-nums tabular-nums</code></span>
</div>

<div class="seite">
  <header class="kopf">
    <a class="marke" href="#" aria-label="Loretto Physiotherapie, Startseite">
      <svg viewBox="0 0 34 34" aria-hidden="true"><circle cx="17" cy="17" r="16" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M6 21c3.2-2.4 6.4-2.4 9.6 0s6.4 2.4 9.6 0c1-.7 2-1.2 2.8-1.4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/><path d="M17 7.5v9.5M13.5 11.5 17 7.5l3.5 4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
      <span><b>Loretto</b><small>Physiotherapie in Konstanz</small></span>
    </a>
    <nav class="nav" aria-label="Hauptnavigation">
      <a href="#">Wobei wir helfen</a>
      <a href="#">Team</a>
      <a href="#" aria-current="page">Kosten</a>
      <a href="#">Anfahrt</a>
    </nav>
    <div class="kopf-rechts">
      <a class="tel" href="tel:+4975313654820"><span>Telefon</span><b class="z">07531 365 48 20</b></a>
      <a class="knopf" href="#termin">Termin vereinbaren</a>
    </div>
  </header>

  <section class="kosten" aria-labelledby="kosten-titel">
    <div>
      <p class="augenbraue">Kosten und Kasse</p>
      <h1 id="kosten-titel">Was eine Behandlung kostet.</h1>
      <p class="lead">Mit Rezept zahlen gesetzlich Versicherte nur die Zuzahlung. Ohne Rezept, privat oder für die Zeit nach der Verordnung gelten diese Preise.</p>

      <div class="tabelle-feld" data-tabelle>
        <p class="modus mono" aria-hidden="true"><i></i><b data-modus-text>tabular-nums</b></p>
        <table class="preise">
          <thead>
            <tr><th scope="col">Behandlung</th><th scope="col" class="r dauer-kopf">Dauer</th><th scope="col" class="r">Selbstzahler</th></tr>
          </thead>
          <tbody>
            <tr><td><b>Beweglich werden, Kraft aufbauen</b><small>Krankengymnastik<span class="d-mobil"> · <span class="z">20</span> Min</span></small></td><td class="r dauer"><span class="z">20</span> Min</td><td class="r preis"><span class="ab">ab</span><span class="z">32<span class="k">,</span>50</span><span class="eur">€</span></td></tr>
            <tr><td><b>Steife Gelenke sanft lösen</b><small>Manuelle Therapie<span class="d-mobil"> · <span class="z">25</span> Min</span></small></td><td class="r dauer"><span class="z">25</span> Min</td><td class="r preis"><span class="ab">ab</span><span class="z">41<span class="k">,</span>10</span><span class="eur">€</span></td></tr>
            <tr><td><b>Schwellungen abbauen</b><small>Manuelle Lymphdrainage<span class="d-mobil"> · <span class="z">45</span> Min</span></small></td><td class="r dauer"><span class="z">45</span> Min</td><td class="r preis"><span class="ab">ab</span><span class="z">52<span class="k">,</span>80</span><span class="eur">€</span></td></tr>
            <tr><td><b>Training an Geräten</b><small>Gerätegestützte Krankengymnastik<span class="d-mobil"> · <span class="z">60</span> Min</span></small></td><td class="r dauer"><span class="z">60</span> Min</td><td class="r preis"><span class="ab">ab</span><span class="z">69<span class="k">,</span>00</span><span class="eur">€</span></td></tr>
            <tr><td><b>Verspannungen lockern</b><small>Klassische Massage<span class="d-mobil"> · <span class="z">20</span> Min</span></small></td><td class="r dauer"><span class="z">20</span> Min</td><td class="r preis"><span class="ab">ab</span><span class="z">27<span class="k">,</span>90</span><span class="eur">€</span></td></tr>
            <tr><td><b>Wärme vor der Behandlung</b><small>Fango<span class="d-mobil"> · <span class="z">20</span> Min</span></small></td><td class="r dauer"><span class="z">20</span> Min</td><td class="r preis"><span class="ab">ab</span><span class="z">14<span class="k">,</span>70</span><span class="eur">€</span></td></tr>
            <tr><td><b>Wir kommen zu Ihnen</b><small>Hausbesuch, Zuschlag je Termin</small></td><td class="r dauer">&nbsp;</td><td class="r preis"><span class="ab">ab</span><span class="z">11<span class="k">,</span>10</span><span class="eur">€</span></td></tr>
          </tbody>
        </table>
        <div class="hilfe" aria-hidden="true" data-hilfe-ebene></div>
        <p class="fuss"><span>Stand <span class="z">1. Oktober 2026</span>. Heilbehandlungen sind umsatzsteuerfrei.</span><span class="mono" data-versatz aria-hidden="true">Komma-Versatz 0,0 px</span></p>
      </div>
    </div>

    <aside class="rechner" id="termin" aria-labelledby="rechner-titel">
      <h2 id="rechner-titel">Ihre Serie, selbst bezahlt</h2>
      <p>Wählen Sie Behandlung und Anzahl. Wir rechnen offen, ohne Pakete und ohne Kleingedrucktes.</p>
      <fieldset class="wahl">
        <legend>Behandlung</legend>
        <label><input type="radio" name="beh" value="3250" data-name="Krankengymnastik"><span>Krankengymnastik</span><span class="z">32,50 €</span></label>
        <label><input type="radio" name="beh" value="4110" data-name="Manuelle Therapie" checked><span>Manuelle Therapie</span><span class="z">41,10 €</span></label>
        <label><input type="radio" name="beh" value="5280" data-name="Lymphdrainage"><span>Lymphdrainage</span><span class="z">52,80 €</span></label>
      </fieldset>
      <div class="zeile2">
        <div class="zahl-feld">
          <p class="bez" id="anzahl-bez">Termine</p>
          <div class="stepper" role="group" aria-labelledby="anzahl-bez">
            <button type="button" data-schritt="-1" aria-label="Einen Termin weniger">−</button>
            <output class="z" data-anzahl aria-live="polite">6</output>
            <button type="button" data-schritt="1" aria-label="Einen Termin mehr">+</button>
          </div>
        </div>
        <label class="fango"><input type="checkbox" data-fango> Fango vorab</label>
      </div>
      <div class="posten">
        <div><span data-posten-1>6 × Manuelle Therapie</span><span class="z" data-betrag-1>246,60 €</span></div>
        <div data-fango-zeile hidden><span data-posten-2>6 × Fango</span><span class="z" data-betrag-2>88,20 €</span></div>
      </div>
      <div class="summe-feld">
        <span class="bez">Gesamt</span>
        <div>
          <p class="summe"><span class="z" data-summe>246,60</span><span class="eur">€</span><span class="breite" aria-hidden="true"></span></p>
          <span class="breite-wert mono" data-breite aria-hidden="true">Breite 0 px</span>
        </div>
      </div>
      <a class="knopf" href="#">Termin vereinbaren</a>
      <p class="hinweis">Zahlung nach jedem Termin mit Karte oder bar. Die Rechnung können Sie bei Privatkasse oder Beihilfe einreichen.</p>
    </aside>
  </section>

  <dl class="zeiten">
    <div><dt>Montag bis Donnerstag</dt><dd class="z">07:30 – 19:00</dd></div>
    <div><dt>Freitag</dt><dd class="z">07:30 – 15:30</dd></div>
    <div><dt>Samstag</dt><dd class="z">09:00 – 12:00</dd><dd>nur Hausbesuche</dd></div>
    <div><dt>Außerhalb der Sprechzeiten</dt><dd>Bereitschaftsdienst <a class="z" href="tel:116117">116 117</a></dd><dd>Lebensgefahr <a class="z" href="tel:112">112</a></dd></div>
  </dl>
</div>

<dl class="werte" data-werte></dl>

<script>
(() => {
  const html = document.documentElement;
  const reduziert = () => html.classList.contains('reduziert');
  const $ = s => document.querySelector(s);
  const knoepfe = [...document.querySelectorAll('[data-modus]')];
  const cssText = $('[data-css]');
  const modusText = $('[data-modus-text]');
  const ebene = $('[data-hilfe-ebene]');
  const feld = $('[data-tabelle]');
  const tabelle = feld.querySelector('table');
  const versatzText = $('[data-versatz]');
  const anzahlOut = $('[data-anzahl]');
  const fango = $('[data-fango]');
  const fangoZeile = $('[data-fango-zeile]');
  const summe = $('[data-summe]');
  const breiteText = $('[data-breite]');
  const werteListe = $('[data-werte]');
  const nullSchalter = $('[data-null]');
  const hilfeSchalter = $('[data-hilfe]');
  const plus = $('[data-schritt="1"]');
  const FANGO = 1470;

  const CSS = {
    prop: 'lining-nums proportional-nums',
    tab: 'lining-nums tabular-nums',
    mono: 'Geist Mono (Ziffern von Haus aus gleich breit)'
  };
  const NAME = { prop: 'proportional-nums', tab: 'tabular-nums', mono: 'Mono-Ziffern' };

  let modus = 'tab', anzahl = 6;

  const euro = c => (c / 100).toFixed(2).replace('.', ',').replace(/\B(?=(\d{3})+(?!\d))/g, '.');
  const fmt = n => n.toFixed(1).replace('.', ',');

  /* Hilfslinien: Kommas der Preisspalte messen */
  const kommas = [...feld.querySelectorAll('.preis .k')];
  const marken = kommas.map(() => { const m = document.createElement('span'); m.className = 'marke-k'; ebene.appendChild(m); return m; });
  const achse = document.createElement('span'); achse.className = 'achse'; ebene.prepend(achse);
  let achseX = null;

  function messen() {
    const f = feld.getBoundingClientRect();
    const xs = kommas.map(k => { const r = k.getBoundingClientRect(); return r.left + r.width / 2 - f.left; });
    // Achse = Lage der Kommas mit Tabellenziffern (alle gleich): die erste Zeile im Tabellen-Modus
    if (modus !== 'prop' || achseX === null) achseX = xs[0];
    achse.style.translate = achseX + 'px 0';
    achse.style.height = (tabelle.getBoundingClientRect().bottom - f.top - 26) + 'px';
    let max = 0;
    kommas.forEach((k, i) => {
      const r = k.getBoundingClientRect();
      const d = xs[i] - achseX;
      max = Math.max(max, Math.abs(d));
      marken[i].style.top = (r.bottom - f.top + 1) + 'px';
      marken[i].style.translate = xs[i] + 'px 0';
      marken[i].classList.toggle('weg', Math.abs(d) > .5);
    });
    versatzText.textContent = 'Komma-Versatz ' + fmt(max) + ' px';
    const b = summe.getBoundingClientRect().width;
    breiteText.textContent = 'Breite der Zahl ' + fmt(b) + ' px';
    return max;
  }

  function zeigeWerte() {
    const w = [
      ['Ziffern', modus === 'mono' ? 'Geist Mono 450, .9em, letter-spacing -.03em' : 'font-variant-numeric: ' + CSS[modus] + (html.classList.contains('nullstrich') ? ' slashed-zero' : '')],
      ['Schrift', 'Google Sans Flex (Ziffern ohne Feature proportional: 1 = .37em, 0 = .65em)'],
      ['Überschrift', "Hedvig Letters Serif 400, 'opsz' 24"],
      ['Preisspalte', 'text-align: right, 1.1875rem / 500'],
      ['Übergang', 'Hilfslinien 250ms cubic-bezier(0.2, 0, 0.38, 0.9), UI 150ms']
    ];
    werteListe.innerHTML = w.map(([a, b]) => `<div><dt>${a}</dt><dd>${b}</dd></div>`).join('');
  }

  function setzeModus(m) {
    modus = m;
    html.dataset.ziffern = m;
    knoepfe.forEach(k => k.setAttribute('aria-pressed', k.dataset.modus === m));
    const n = html.classList.contains('nullstrich') && m !== 'mono' ? ' slashed-zero' : '';
    cssText.textContent = m === 'mono' ? "font-family: 'Geist Mono'" : 'font-variant-numeric: ' + CSS[m] + n;
    modusText.textContent = NAME[m] + n;
    zeigeWerte();
    requestAnimationFrame(messen);
  }

  function rechnen() {
    const wahl = document.querySelector('[name="beh"]:checked');
    const preis = +wahl.value;
    anzahlOut.textContent = anzahl;
    const b1 = preis * anzahl, b2 = fango.checked ? FANGO * anzahl : 0;
    $('[data-posten-1]').textContent = anzahl + ' × ' + wahl.dataset.name;
    $('[data-betrag-1]').textContent = euro(b1) + ' €';
    $('[data-posten-2]').textContent = anzahl + ' × Fango';
    $('[data-betrag-2]').textContent = euro(b2) + ' €';
    fangoZeile.hidden = !fango.checked;
    summe.textContent = euro(b1 + b2);
    messen();
  }

  document.querySelectorAll('[data-schritt]').forEach(b => b.addEventListener('click', e => {
    if (e.isTrusted) stopp();
    anzahl = Math.max(1, Math.min(12, anzahl + +b.dataset.schritt));
    rechnen();
  }));
  document.querySelectorAll('[name="beh"]').forEach(r => r.addEventListener('change', () => { stopp(); rechnen(); }));
  fango.addEventListener('change', e => { if (e.isTrusted) stopp(); rechnen(); });
  knoepfe.forEach(k => k.addEventListener('click', () => { stopp(); setzeModus(k.dataset.modus); }));
  nullSchalter.addEventListener('change', () => { html.classList.toggle('nullstrich', nullSchalter.checked); setzeModus(modus); });
  hilfeSchalter.addEventListener('change', () => html.classList.toggle('ohne-hilfe', !hilfeSchalter.checked));
  addEventListener('resize', messen);
  if (document.fonts) document.fonts.ready.then(() => { achseX = null; setzeModus(modus); rechnen(); });

  /* Vorführung: der Zeiger tippt auf „+“, die Ziffernart wechselt jede Runde */
  let uhren = [];
  const spaeter = (fn, ms) => uhren.push(setTimeout(fn, ms));
  function stopp() { uhren.forEach(clearTimeout); uhren = []; html.classList.remove('vorfuehrung'); plus.classList.remove('gedrueckt'); }
  function tippe() {
    plus.classList.add('gedrueckt');
    spaeter(() => plus.classList.remove('gedrueckt'), 150);
    plus.click();
  }
  function runde(m) {
    setzeModus(m);
    anzahl = 3; fango.checked = false; rechnen();
    const takt = reduziert() ? 900 : 620;
    for (let i = 1; i <= 6; i++) spaeter(tippe, 500 + i * takt);
    spaeter(() => { fango.checked = true; rechnen(); }, 500 + 3.5 * takt);
    spaeter(() => runde(m === 'tab' ? 'prop' : 'tab'), 500 + 7 * takt + 900);
  }
  function vorfuehren() {
    stopp();
    html.classList.add('vorfuehrung');
    runde('prop');
  }
  function pause() { uhren.forEach(clearTimeout); uhren = []; plus.classList.remove('gedrueckt'); }
  function weiter() { if (html.classList.contains('vorfuehrung')) runde(modus === 'tab' ? 'prop' : 'tab'); }
  function neustart() {
    stopp();
    anzahl = 6; fango.checked = false;
    document.querySelector('[name="beh"][value="4110"]').checked = true;
    setzeModus('tab'); rechnen();
  }

  setzeModus('tab');
  rechnen();

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