Zum Inhalt
← Typo-Techniken

Silbentrennung für deutsche Komposita

Proof-Rahmen einer Verdener Fahrschule bei 320 px: links läuft „Führerscheinausbildung“ über, rechts trennt das Regelwerk an den Wortfugen, und eine Live-Messung zählt Überlauf, Trennungen und Zwangsumbrüche.

Große H1 und H2 auf dem Telefon, besonders Fahrschule, Kanzlei, Pflege und Verwaltung mit langen Fachwörtern.

Deutsche Wörter wie „Führerscheinausbildung“ oder „Sonderfahrtenpauschale“ sind länger als eine Telefonzeile in großer Schrift. Ohne Regeln läuft das Wort über den Rand, wird abgeschnitten oder reißt an beliebiger Stelle. Das Muster setzt ein festes Regelwerk: lang="de" am Dokument, hyphens: auto mit hyphenate-limit-chars für Mindestlängen, &shy; an den Fugen großer Headlines, <wbr> für Schrägstriche und Adressen, geschützter Bindestrich (U+2011) für Kürzel wie „B‑197“ und text-wrap: balance bzw. pretty für ruhige Zeilen. Die Demo zeigt Vorher und Nachher in einem Rahmen von 320 px.

Ein deutsches Kompositum, das aus dem Bildschirm läuft, ist der häufigste Fehler auf übersetzten oder generierten Seiten. Wer an der Wortfuge trennt („Führerschein-/ausbildung“), liest das Wort in einem Zug. Wer mitten in der Silbe oder nach zwei Buchstaben trennt, stolpert. Feste Regeln machen große Headlines auf dem Telefon möglich, ohne die Schrift kleiner zu drehen.

Wann einsetzen

Auf jeder deutschen Seite. Pflicht bei großen Headlines (ab etwa 2,5rem auf dem Telefon), schmalen Spalten (Karten, Preislisten, Tabellenköpfe, Navigation im Footer) und bei Branchen mit langen Fachwörtern: Fahrschule, Kanzlei, Praxis, Pflege, Handwerk, Industrie, Verwaltung.

Wann nicht

Nicht bei Markennamen, Eigennamen und Wortmarken: dort hyphens: none und lieber kleiner setzen oder umformulieren. Nicht bei Versalzeilen mit großer Laufweite. Nicht bei Telefonnummern, Preisen, Kürzeln und Codes. Automatische Trennung ersetzt kein gutes Umschreiben: Wenn eine Headline vier Trennungen braucht, ist sie zu lang.

Varianten

  • Headline: große H1 mit &shy; an den Wortfugen, hyphens: manual, text-wrap: balance
  • Spalte: schmale Karten und Preisliste mit hyphens: auto und hyphenate-limit-chars: 10 4 4
  • Feinsatz: <wbr> nach Schrägstrich und in Adressen, geschützter Bindestrich in Kürzeln, geschütztes Leerzeichen in Einheiten

Umsetzung

  1. lang="de" am <html>, fremdsprachige Wörter mit eigenem lang.
  2. Fließtext und schmale Spalten: hyphens: auto; hyphenate-limit-chars: 10 4 4 (Wort ab 10 Zeichen, mindestens 4 vor und 4 nach der Trennung); overflow-wrap: break-word als Notbremse.
  3. Große Headlines: hyphens: manual und &shy; an den Wortfugen von Hand (Führer&shy;schein&shy;ausbildung). So trennt jeder Browser gleich, auch Safari ohne hyphenate-limit-chars. Dazu text-wrap: balance.
  4. Lauftext: text-wrap: pretty gegen einzelne Wörter in der letzten Zeile.
  5. <wbr> nach Schrägstrichen („Theorie/<wbr>Praxis“), in E-Mail-Adressen und URLs.
  6. U+2011 (geschützter Bindestrich) in Kürzeln wie „B‑197“, U+00A0 zwischen Zahl und Einheit.
  7. Marken und Namen: hyphens: none (Klasse .ohne-trennung).
  8. Prüfen bei 320 px und 200 % Zoom: kein Wort läuft über, keine Trennung nach 2 Buchstaben.

Robust umsetzen

Telefon. Das Muster ist für das Telefon gemacht: bei 320 bis 390 px Breite läuft kein Wort über. Die Trennstellen in Headlines stehen fest, damit sie auf allen Geräten gleich aussehen.

Ohne Bewegung. Das Muster bewegt nichts. In der Demo springt der Rahmen ohne Gleiten auf die neue Breite.

Barrierefrei. Weiche Trennzeichen stören Screenreader nicht, das Wort wird ganz vorgelesen. Kein Text in Bildern, keine Verkleinerung unter 16 px für Lauftext. Kein waagerechtes Scrollen bei 320 px (WCAG 1.4.10). Trennungen bei 200 % Zoom und erhöhten Textabständen prüfen (WCAG 1.4.12).

Code

<!DOCTYPE html>
<!--
  Silbentrennung für deutsche Komposita
  Fundus · Typo-Techniken · https://cecodesign.de/lab/fundus/typo-techniken/silbentrennung-komposita/
  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>Silbentrennung für deutsche Komposita · 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: 'Atkinson Hyperlegible Next'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/atkinson-hyperlegible-next:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 200 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; }

/* Rezept fahrschule-freundlich-buchbar: Creme, Navy, Himmelblau, Kennzeichen-Gelb nur als Fläche */
:root {
  --grund: #F8F8EC; --wechsel: #EEF0D8; --text: #142B50; --leise: #566175; --linie: #D4D7D3;
  --himmel: #3FBADF; --gelb: #FFD60A; --fehler: #B3261E; --gut: #1D6B3A;
  --schrift-headline: 'Bricolage Grotesque', system-ui, sans-serif;
  --schrift-text: 'Atkinson Hyperlegible Next', system-ui, sans-serif;
  --kurve: cubic-bezier(0.16, 1, 0.3, 1);      /* Einblendungen und Rahmenbreite (UI-Stil des Rezepts) */
  --feder: cubic-bezier(0.34, 1.56, 0.64, 1);  /* nur Transform kleiner Elemente (Schalter-Knopf) */
  --dauer: 450ms; --ui: 150ms;
  --rahmen: 320px;
}
* { box-sizing: border-box; margin: 0; }
html { background: var(--grund); color: var(--text); font-family: var(--schrift-text); -webkit-font-smoothing: antialiased; }
:root:has(:focus-visible) { scroll-behavior: auto; }
html { scroll-padding-bottom: 96px; } /* Fokus nicht unter dem Pult */
body { min-height: 100svh; }
:focus-visible { outline: 2px solid var(--text); outline-offset: 3px; }
.label { font: 500 11px/1.3 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; color: var(--leise); }

/* ——— Kopf der erfundenen Fahrschule ——— */
.kopf { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px 24px; padding: 18px clamp(16px, 4vw, 56px); border-bottom: 1px solid var(--linie); }
.marke { display: flex; align-items: center; gap: 12px; text-decoration: none; color: inherit; }
.marke b { font: 800 22px/1 var(--schrift-headline); font-variation-settings: 'opsz' 48; letter-spacing: -.03em; hyphens: none; }
.marke span { background: var(--gelb); color: var(--text); border-radius: 999px; padding: 5px 11px; font: 600 13px/1 var(--schrift-text); }
.kopf .label { color: var(--leise); }

/* ——— Bühne ——— */
main { display: grid; grid-template-columns: minmax(0, 1fr); grid-template-areas: 'intro' 'buehne' 'regeln'; gap: clamp(24px, 3.4vw, 40px) clamp(28px, 4vw, 56px); padding: clamp(24px, 4vw, 52px) clamp(16px, 4vw, 56px) 96px; max-width: 1360px; margin-inline: auto; }
@media (min-width: 1080px) { main { grid-template-columns: minmax(280px, 340px) minmax(0, 1fr); grid-template-areas: 'intro buehne' 'regeln buehne'; grid-template-rows: auto 1fr; } }
.erklaer { grid-area: intro; } .steuer { grid-area: regeln; } .vergleich { grid-area: buehne; }
.erklaer h1 { font: 700 clamp(2rem, 1.2rem + 2.4vw, 3rem)/1 var(--schrift-headline); font-variation-settings: 'opsz' 96; letter-spacing: -.02em; text-wrap: balance; hyphens: manual; }
.erklaer > p:not(.label) { font-size: 1.0625rem; line-height: 1.6; margin-top: 16px; max-width: 46ch; text-wrap: pretty; hyphens: auto; }
.erklaer .label { margin-bottom: 14px; }

/* Regel-Schalter: echte Checkboxen, gestaltet als Pillen */
.regeln { border: 0; padding: 0; display: grid; gap: 8px; }
.regeln legend { margin-bottom: 10px; }
.regel { position: relative; display: grid; grid-template-columns: 38px minmax(0, 1fr); gap: 12px; align-items: center; padding: 10px 14px 10px 10px; border-radius: 14px; background: var(--wechsel); cursor: pointer; transition: background-color var(--ui) var(--kurve); }
.regel:hover { background: #E4E7C8; }
.regel input { position: absolute; opacity: 0; inset: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.regel .schalter { width: 38px; height: 22px; border-radius: 999px; background: #fff; border: 1.5px solid var(--leise); position: relative; transition: background-color var(--ui) var(--kurve), border-color var(--ui) var(--kurve); }
.regel .schalter::after { content: ""; position: absolute; top: 2px; left: 2px; width: 15px; height: 15px; border-radius: 50%; background: var(--leise); transition: translate var(--dauer) var(--feder), background-color var(--ui); }
.regel input:checked + .schalter { background: var(--text); border-color: var(--text); }
.regel input:checked + .schalter::after { translate: 16px 0; background: var(--gelb); }
.regel input:focus-visible + .schalter { outline: 2px solid var(--text); outline-offset: 3px; }
.regel b { display: block; font: 600 15px/1.25 var(--schrift-text); }
.regel code { font: 400 12px/1.4 'Geist Mono', ui-monospace, monospace; color: var(--leise); overflow-wrap: anywhere; }

.breite { margin-top: 22px; display: grid; gap: 8px; }
.breite div { display: flex; justify-content: space-between; align-items: baseline; }
.breite output { font: 600 15px/1 var(--schrift-text); font-variant-numeric: tabular-nums; }
.breite input { width: 100%; accent-color: var(--text); height: 28px; }
.browser { margin-top: 18px; font-size: 14px; line-height: 1.5; color: var(--leise); }
.browser b { color: var(--text); }

/* ——— Proof-Rahmen ——— */
.vergleich { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(16px, 2vw, 28px); align-items: start; min-width: 0; }
@media (max-width: 760px) { .vergleich { grid-template-columns: minmax(0, 1fr); } }
.blatt { min-width: 0; }
.blatt-kopf { display: flex; justify-content: space-between; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; min-height: 34px; }
.blatt-kopf .label b { color: var(--text); font-weight: 600; }
.blatt-kopf select { font: 500 11px/1 'Geist Mono', ui-monospace, monospace; letter-spacing: .06em; color: var(--text); background: var(--grund); border: 1px solid var(--leise); border-radius: 999px; padding: 7px 10px; }
.zone { position: relative; overflow: hidden; border-radius: 20px; background: repeating-linear-gradient(-45deg, rgba(179, 38, 30, .07) 0 6px, transparent 6px 12px), var(--wechsel); padding: 34px 0 0; }
.zone::before { /* Lineal */ content: attr(data-breite); position: absolute; top: 9px; left: 14px; font: 500 10.5px/1 'Geist Mono', ui-monospace, monospace; letter-spacing: .1em; color: var(--leise); }
.skala { zoom: var(--zoom, 1); width: calc(var(--rahmen) + 36px); }
.rahmen { width: var(--rahmen); background: var(--grund); border-right: 2px solid var(--text); min-height: 100%; padding: 22px 16px 26px; transition: width var(--dauer) var(--kurve); position: relative; }
.rahmen::after { content: ""; position: absolute; top: -10px; right: -7px; width: 12px; height: 12px; border-radius: 50%; background: var(--text); }
.messwerte { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.wert { font: 500 11px/1 'Geist Mono', ui-monospace, monospace; letter-spacing: .06em; padding: 6px 9px; border-radius: 999px; background: var(--wechsel); color: var(--text); font-variant-numeric: tabular-nums; }
.wert.schlecht { background: #F6DAD6; color: #7E1A14; }
.wert.gut { background: #D7EBDD; color: #14502A; }

/* Inhalt im Rahmen: Rezept-Typo bei 320 px */
.probe { font-family: var(--schrift-text); color: var(--text); }
.probe .kicker { font: 600 13px/1 var(--schrift-text); display: inline-block; background: var(--gelb); border-radius: 999px; padding: 6px 10px; margin-bottom: 16px; }
.probe h2 { font: 700 2.5rem/.98 var(--schrift-headline); font-variation-settings: 'opsz' 96; letter-spacing: -.02em; }
.probe h3 { font: 700 1.75rem/1.02 var(--schrift-headline); font-variation-settings: 'opsz' 72; letter-spacing: -.015em; margin-top: 26px; }
.probe p { font-size: 1.125rem; line-height: 1.6; margin-top: 14px; }
.probe .knopf { display: inline-block; margin-top: 18px; background: var(--text); color: var(--grund); border-radius: 999px; padding: 12px 18px; font-weight: 600; font-size: 1rem; }
.probe .karten { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin-top: 16px; }
.probe .karte { background: var(--wechsel); border-radius: 14px; padding: 12px 10px 14px; min-width: 0; }
.probe .karte h4 { font: 700 1.25rem/1.08 var(--schrift-headline); font-variation-settings: 'opsz' 36; letter-spacing: -.01em; }
.probe .karte span { display: block; margin-top: 8px; font-size: 1rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.probe .fein { font-size: 1.375rem; line-height: 1.45; }
.probe .fein + .fein { margin-top: 18px; }
.probe .marke-im-text { hyphens: none; }
.probe .ueber { text-decoration: underline wavy var(--fehler) 1.5px; text-underline-offset: 5px; }

/* Rohsatz: nichts gesetzt, wie aus einer englischen Vorlage */
.roh .probe * { hyphens: manual; }
.roh.zwang .probe * { overflow-wrap: anywhere; }

/* Regelwerk: jede Regel eine Klasse am Rahmen */
.r-auto .probe p, .r-auto .probe h4, .r-auto .probe h2, .r-auto .probe h3 { hyphens: auto; }
.r-fugen .probe h2, .r-fugen .probe h3, .r-fugen .probe h4 { hyphens: manual; }
.r-grenze .probe * { hyphenate-limit-chars: 10 4 4; }
.r-satz .probe h2, .r-satz .probe h3, .r-satz .probe h4 { text-wrap: balance; }
.r-satz .probe p { text-wrap: pretty; }
.r-not .probe * { overflow-wrap: break-word; }

/* Code der aktiven Regeln */
.code { margin-top: 22px; background: var(--text); color: #E8ECF4; border-radius: 14px; padding: 14px 16px; font: 400 12.5px/1.6 'Geist Mono', ui-monospace, monospace; white-space: pre-wrap; overflow-wrap: anywhere; }
.code i { font-style: normal; color: #9FB0CC; }
.code b { font-weight: 500; color: var(--gelb); }

/* Varianten-Umschalter (Pult) */
.pult { position: fixed; right: 16px; bottom: 16px; z-index: 20; display: flex; flex-wrap: wrap; gap: 4px; align-items: center; padding: 5px; border-radius: 999px; background: var(--text); box-shadow: 0 10px 30px rgba(20, 43, 80, .22); }
.pult .label { color: #C9D2E3; padding: 0 8px 0 10px; }
.pult button { font: 500 11px/1 'Geist Mono', ui-monospace, monospace; letter-spacing: .1em; text-transform: uppercase; color: #E8ECF4; background: transparent; border: 0; border-radius: 999px; padding: 9px 12px; cursor: pointer; transition: background-color var(--ui) var(--kurve), color var(--ui) var(--kurve); }
.pult button:hover { background: rgba(255, 255, 255, .1); }
.pult button[aria-pressed="true"] { background: var(--gelb); color: var(--text); }
.pult button:focus-visible { outline: 2px solid var(--gelb); outline-offset: 2px; }

/* Vorschau 960 × 720: nur Bühne */
.vorschau body { overflow: hidden; }
.vorschau .kopf, .vorschau .erklaer, .vorschau .code, .vorschau .browser, .vorschau .breite { display: none; }
.vorschau main { grid-template-columns: 250px minmax(0, 1fr); grid-template-areas: 'regeln buehne'; grid-template-rows: auto; padding-top: 28px; gap: 28px; }

:root.reduziert * { transition-duration: .01ms !important; animation-duration: .01ms !important; }
@media (hover: none) { .regel { padding-block: 13px; } }
@media (max-width: 360px) { .pult .label { display: none; } }
@media (forced-colors: active) {
  button, [role=button], [role=tab], [role=switch], input, select, textarea { border: 1px solid ButtonText !important; }
  .regel .schalter { border: 1px solid ButtonText; } .rahmen { border-right-color: CanvasText; }
}
</style>
</head>
<body>
<header class="kopf">
  <a class="marke" href="#start" aria-label="Fahrschule Kurvenlage, Verden (Aller)"><b>Kurvenlage</b><span>Fahrschule · Verden (Aller)</span></a>
  <p class="label">Typo-Technik · Silbentrennung</p>
</header>
<main id="start">
  <section class="erklaer" aria-labelledby="h-erklaer">
    <p class="label" id="v-label">Variante: Headline</p>
    <h1 id="h-erklaer">Lange Wörter, schmale Zeilen.</h1>
    <p id="v-text"></p>
  </section>
  <section class="steuer" aria-label="Regeln und Rahmenbreite">
    <fieldset class="regeln" id="regeln">
      <legend class="label">Regelwerk im Nachher-Rahmen</legend>
    </fieldset>
    <div class="breite">
      <div><label class="label" for="breite">Rahmenbreite</label><output id="breite-wert" for="breite">320 px</output></div>
      <input type="range" id="breite" min="320" max="560" step="8" value="320">
    </div>
    <p class="browser" id="browser" role="status"></p>
    <pre class="code" id="code" aria-label="CSS der aktiven Regeln"></pre>
  </section>

  <section class="vergleich" aria-label="Vorher und Nachher bei gleicher Breite">
    <div class="blatt">
      <div class="blatt-kopf">
        <p class="label"><b>Vorher</b> · ohne Regeln</p>
        <select id="roh-art" aria-label="Fehlerbild im Vorher-Rahmen"><option value="ueberlauf">Fehler: Überlauf</option><option value="zwang">Fehler: Zwangsumbruch</option></select>
      </div>
      <div class="zone" data-breite="320 px">
        <div class="skala"><div class="rahmen roh" id="rahmen-roh" lang=""></div></div>
      </div>
      <div class="messwerte" id="mess-roh" aria-live="polite"></div>
    </div>
    <div class="blatt">
      <div class="blatt-kopf"><p class="label"><b>Nachher</b> · Regelwerk</p></div>
      <div class="zone" data-breite="320 px">
        <div class="skala"><div class="rahmen" id="rahmen-gut" lang="de"></div></div>
      </div>
      <div class="messwerte" id="mess-gut" aria-live="polite"></div>
    </div>
  </section>
</main>

<nav class="pult" aria-label="Variante wählen">
  <span class="label">Variante</span>
  <button type="button" data-v="headline" aria-pressed="true">Headline</button>
  <button type="button" data-v="spalte" aria-pressed="false">Spalte</button>
  <button type="button" data-v="feinsatz" aria-pressed="false">Feinsatz</button>
</nav>

<script>
(() => {
  const H = document.documentElement;
  const reduziert = () => H.classList.contains('reduziert');
  const $ = s => document.querySelector(s);
  const SHY = '­', NBSP = ' ', NBH = '‑';

  /* Inhalte. „|“ = Wortfuge (wird zu &shy;), „^“ = <wbr>, „~“ = geschütztes Leerzeichen, „‑“ steht als „‡“ */
  const VARIANTEN = {
    headline: {
      name: 'Headline',
      text: '„Führerscheinausbildung“ hat 22 Buchstaben. In der Headline des Rezepts sind das bei 320 px rund 480 px Breite. Vorher ohne Regeln, Nachher mit dem Regelwerk. Schalten Sie die Regeln einzeln und ziehen Sie den Rahmen breiter.',
      html: `<div class="probe">
        <span class="kicker">Klasse B · ab 17 Jahren</span>
        <h2>Führer|schein|ausbildung für Berufs|tätige</h2>
        <p>Theorie am Abend, Fahrstunden ab 6:30~Uhr vor der Arbeit. Im Schnitt dauert die Ausbildung zwölf Wochen, Verkehrsübungsplatz und Autobahnfahrten inklusive.</p>
        <h3>Ferien|intensiv|kurs im Oktober</h3>
        <span class="knopf">Anmeldung starten</span>
      </div>`,
      regeln: ['lang', 'fugen', 'auto', 'grenze', 'satz']
    },
    spalte: {
      name: 'Spalte',
      text: 'Preiskarten im Zweierraster sind auf dem Telefon rund 130\u00A0px breit. Titel trennen an den Wortfugen, der Fließtext automatisch mit Mindestlängen: Wort ab 10 Zeichen, 4 Buchstaben vor und 4 nach dem Strich. Schalten Sie die Fugen ab: Dann trennt der Browser nach Silben, etwa „Lehrma-terialpaket“.',
      html: `<div class="probe">
        <span class="kicker">Preise Klasse B · Stand 04.11.2026</span>
        <h3 style="margin-top:0">Was deine Ausbildung kostet</h3>
        <div class="karten">
          <div class="karte"><h4>Grund|betrag</h4><span>390~€</span></div>
          <div class="karte"><h4>Sonder|fahrten|pauschale</h4><span>12 × 75~€</span></div>
          <div class="karte"><h4>Prüfungs|vorstellung Theorie</h4><span>95~€</span></div>
          <div class="karte"><h4>Prüfungs|vorstellung Praxis</h4><span>185~€</span></div>
          <div class="karte"><h4>Lehr|material|paket</h4><span>89~€</span></div>
          <div class="karte"><h4>Übungs|fahrstunde</h4><span>65~€</span></div>
        </div>
        <p>Prüfungsgebühren des TÜV kommen dazu und gehen direkt an die Prüforganisation.</p>
      </div>`,
      regeln: ['lang', 'fugen', 'auto', 'grenze', 'satz']
    },
    feinsatz: {
      name: 'Feinsatz',
      text: 'Nicht alles ist ein Wort. Adressen brauchen <wbr> als erlaubte Bruchstelle, Kürzel einen geschützten Bindestrich (U+2011), Zahl und Einheit ein geschütztes Leerzeichen. Der Markenname wird nie getrennt.',
      html: `<div class="probe">
        <span class="kicker">Anmeldung</span>
        <p class="fein">Schreib uns an anmeldung@^fahrschulekurvenlage^.de oder komm in die Große Straße~41.</p>
        <p class="fein">Online: fahrschulekurvenlage.de/^anmeldung/^klasse-b197</p>
        <p class="fein">Für Klasse B‡197 rechnen wir mit 2.940~€ für 12~Fahrstunden à 45~Min., Theorie/^Praxis inklusive.</p>
        <p class="fein">Die <span class="marke-im-text">Kurvenlage</span>-Theorie läuft montags und donnerstags von 18:30~Uhr bis 20~Uhr.</p>
      </div>`,
      regeln: ['lang', 'auto', 'wbr', 'schutz', 'marke']
    }
  };

  const REGELN = {
    lang:   { name: 'Sprache Deutsch', code: 'lang="de"', css: '<i>/* HTML */</i>\n&lt;html <b>lang="de"</b>&gt;' },
    fugen:  { name: 'Trennung an Wortfugen', code: 'Führer&amp;shy;schein&amp;shy;ausbildung', css: 'h1, h2 { <b>hyphens: manual</b>; } <i>/* Fugen per &amp;shy; */</i>' },
    auto:   { name: 'Automatische Trennung', code: 'hyphens: auto', css: 'p, h4 { <b>hyphens: auto</b>; }' },
    grenze: { name: 'Mindestlängen', code: 'hyphenate-limit-chars: 10 4 4', css: '* { <b>hyphenate-limit-chars: 10 4 4</b>; } <i>/* Safari: ignoriert */</i>' },
    satz:   { name: 'Ruhige Zeilen', code: 'text-wrap: balance / pretty', css: 'h2, h3, h4 { <b>text-wrap: balance</b>; }\np { <b>text-wrap: pretty</b>; }' },
    wbr:    { name: 'Bruchstellen in Adressen', code: '&lt;wbr&gt; nach @ und /, vor .de', css: '<i>/* HTML */</i> anmeldung@<b>&lt;wbr&gt;</b>fahrschulekurvenlage<b>&lt;wbr&gt;</b>.de' },
    schutz: { name: 'Geschützte Zeichen', code: 'U+00A0 · U+2011', css: '<i>/* Text */</i> 2.940<b>&amp;nbsp;</b>€ · B<b>&amp;#8209;</b>197' },
    marke:  { name: 'Marke nie trennen', code: '.marke { hyphens: none }', css: '.marke { <b>hyphens: none</b>; white-space: nowrap; }' }
  };

  const v0 = new URLSearchParams(location.search).get('v');
  let variante = VARIANTEN[v0] ? v0 : 'headline';
  let an = new Set();
  let breite = 320;

  /* Text nach Regeln aufbereiten */
  function setze(html, mitRegeln) {
    const fugen = mitRegeln && an.has('fugen');
    const wbr = mitRegeln && an.has('wbr');
    const schutz = mitRegeln && an.has('schutz');
    return html
      .replace(/\|/g, fugen ? '&shy;' : '')
      .replace(/\^/g, wbr ? '<wbr>' : '')
      .replace(/~/g, schutz || (mitRegeln && variante !== 'feinsatz') ? '&nbsp;' : ' ')
      .replace(/‡/g, schutz ? NBH : '-');
  }

  function baueRegeln() {
    const fs = $('#regeln');
    fs.querySelectorAll('.regel').forEach(n => n.remove());
    VARIANTEN[variante].regeln.forEach(id => {
      const r = REGELN[id];
      const l = document.createElement('label');
      l.className = 'regel';
      l.innerHTML = `<input type="checkbox" data-regel="${id}" ${an.has(id) ? 'checked' : ''}><span class="schalter" aria-hidden="true"></span><span><b>${r.name}</b><code>${r.code}</code></span>`;
      fs.appendChild(l);
    });
  }

  function render() {
    const V = VARIANTEN[variante];
    const roh = $('#rahmen-roh'), gut = $('#rahmen-gut');
    roh.innerHTML = setze(V.html, false);
    gut.innerHTML = setze(V.html, true);
    roh.classList.toggle('zwang', $('#roh-art').value === 'zwang');
    gut.className = 'rahmen ' + [...an].map(r => 'r-' + r).join(' ');
    gut.setAttribute('lang', an.has('lang') ? 'de' : '');
    if (variante === 'feinsatz' && !an.has('marke')) gut.querySelectorAll('.marke-im-text').forEach(n => n.classList.remove('marke-im-text'));
    roh.querySelectorAll('.marke-im-text').forEach(n => n.classList.remove('marke-im-text'));
    $('#code').innerHTML = V.regeln.filter(r => an.has(r)).map(r => REGELN[r].css).join('\n\n') || '<i>/* keine Regel aktiv: wie links */</i>';
    requestAnimationFrame(() => { messe(roh, $('#mess-roh'), false); messe(gut, $('#mess-gut'), true); passe(); });
  }

  /* Live-Messung: Überlauf, Trennungen, davon an Wortfugen, Zwangsumbrüche */
  function messe(rahmen, ziel, gutSeite) {
    const rr = rahmen.getBoundingClientRect();
    const z = parseFloat(getComputedStyle(rahmen.closest('.skala')).zoom) || 1;
    const innenRechts = rr.right - 16 * z;
    let ueber = 0, trenn = 0, fuge = 0, zwang = 0;
    rahmen.querySelectorAll('.ueber').forEach(n => n.classList.remove('ueber'));
    const walker = document.createTreeWalker(rahmen, NodeFilter.SHOW_TEXT);
    let t;
    while ((t = walker.nextNode())) {
      const s = t.textContent, el = t.parentElement;
      const auto = getComputedStyle(el).hyphens === 'auto' && el.closest('[lang]').getAttribute('lang') === 'de';
      let alt = null, vor = '', letzteFuge = -9;
      for (let i = 0; i < s.length; i++) {
        if (s[i] === SHY) { letzteFuge = i; continue; }
        const R = document.createRange(); R.setStart(t, i); R.setEnd(t, i + 1);
        const rs = R.getClientRects(), q = rs[rs.length - 1];
        if (!q || !q.width) { vor = s[i]; continue; }
        if (q.right > innenRechts + 1) { ueber = Math.max(ueber, (q.right - innenRechts) / z); el.classList.add('ueber'); }
        if (alt && q.top > alt.top + 4 && /[\p{L}\d]/u.test(vor) && /[\p{L}\d]/u.test(s[i])) {
          /* Chromium meldet den ersten Buchstaben nach einer &shy;-Trennung teils noch auf der alten Zeile */
          if (i - letzteFuge <= 2) { trenn++; fuge++; } else if (auto) trenn++; else zwang++;
        }
        alt = q; vor = s[i];
      }
    }
    const w = [];
    w.push(ueber > 0.5 ? `<span class="wert schlecht">Überlauf ${Math.round(ueber)} px</span>` : `<span class="wert gut">kein Überlauf</span>`);
    w.push(`<span class="wert">Trennungen ${trenn}</span>`);
    if (fuge) w.push(`<span class="wert gut">an Wortfugen ${fuge}</span>`);
    if (zwang) w.push(`<span class="wert schlecht">Zwangsumbruch ${zwang}</span>`);
    ziel.innerHTML = w.join('');
  }

  /* Rahmen passend verkleinern (zoom), damit nichts waagerecht scrollt */
  function passe() {
    document.querySelectorAll('.zone').forEach(zone => {
      const platz = zone.clientWidth;
      const noetig = breite + 36;
      zone.querySelector('.skala').style.setProperty('--zoom', Math.min(1, platz / noetig).toFixed(3));
      zone.dataset.breite = breite + ' px' + (platz < noetig ? ' · verkleinert' : '');
    });
  }

  function setzeVariante(v, still) {
    variante = v;
    an = new Set(VARIANTEN[v].regeln);
    document.querySelectorAll('.pult button').forEach(b => b.setAttribute('aria-pressed', b.dataset.v === v ? 'true' : 'false'));
    $('#v-label').textContent = 'Variante: ' + VARIANTEN[v].name;
    $('#v-text').textContent = VARIANTEN[v].text;
    if (!still) { const q = new URLSearchParams(location.search); q.set('v', v); history.replaceState(null, '', '?' + q.toString()); }
    baueRegeln(); render();
  }

  function setzeBreite(px) {
    breite = px;
    H.style.setProperty('--rahmen', px + 'px');
    $('#breite').value = px; $('#breite-wert').textContent = px + ' px';
    passe();
    clearTimeout(setzeBreite.t);
    setzeBreite.t = setTimeout(render, reduziert() ? 0 : 470);
  }

  /* Trennt dieser Browser Deutsch? */
  function pruefeBrowser() {
    const p = document.createElement('p');
    p.lang = 'de'; p.style.cssText = 'position:absolute;visibility:hidden;width:150px;font:20px serif;hyphens:auto';
    p.textContent = 'Führerscheinausbildung';
    document.body.appendChild(p);
    const zeilen = Math.round(p.offsetHeight / parseFloat(getComputedStyle(p).lineHeight || 24));
    const ok = p.offsetHeight > 30; p.remove();
    const grenze = CSS.supports('hyphenate-limit-chars', '10 4 4'), pretty = CSS.supports('text-wrap', 'pretty');
    $('#browser').innerHTML = `Dieser Browser trennt Deutsch automatisch: <b>${ok ? 'ja' : 'nein'}</b> · Mindestlängen: <b>${grenze ? 'ja' : 'nein'}</b> · text-wrap: pretty: <b>${pretty ? 'ja' : 'nein'}</b>`;
    return zeilen;
  }

  document.addEventListener('change', e => {
    const id = e.target.dataset && e.target.dataset.regel;
    if (id) { e.target.checked ? an.add(id) : an.delete(id); render(); }
    if (e.target.id === 'roh-art') render();
  });
  $('#breite').addEventListener('input', e => { pause(); setzeBreite(+e.target.value); });
  document.querySelectorAll('.pult button').forEach(b => b.addEventListener('click', () => { pause(); setzeVariante(b.dataset.v); }));
  new ResizeObserver(() => { passe(); clearTimeout(passe.t); passe.t = setTimeout(render, 120); }).observe(document.body);

  /* Vorführung: Regeln einzeln einschalten, dann Rahmen atmen lassen */
  let schritte = [], lauf = null;
  function neustart() {
    clearTimeout(lauf);
    setzeBreite(320);
    an = new Set(VARIANTEN[variante].regeln);
    baueRegeln(); render();
  }
  function vorfuehren() {
    clearTimeout(lauf);
    const regeln = VARIANTEN[variante].regeln;
    an = new Set(); baueRegeln(); setzeBreite(320); render();
    schritte = [];
    regeln.forEach(r => schritte.push(() => { an.add(r); baueRegeln(); render(); }));
    schritte.push(() => setzeBreite(reduziert() ? 400 : 416));
    schritte.push(() => setzeBreite(320));
    let i = 0;
    const takt = () => { if (i < schritte.length) { schritte[i++](); lauf = setTimeout(takt, 1100); } else { lauf = setTimeout(vorfuehren, 1600); } };
    lauf = setTimeout(takt, 900);
  }
  function pause() { clearTimeout(lauf); }
  function weiter() { vorfuehren(); }

  pruefeBrowser();
  setzeVariante(variante, true);
  if (document.fonts) document.fonts.ready.then(render);

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