Styleguide

Pläne und Bälle behalten ihre Echtfarben. Die Marke rahmt sie in Petrol und Creme, die Anlagenfarbe taucht als kleiner Farbchip auf.

Alle Beispiele sind mit den echten Partials und CSS-Klassen der Seite gerendert; die Code-Blöcke lesen die Dateien live aus dem Repo.

Palette

Petrol #003B40 --pe-petrol Marke, Text, Rahmen, Auswahlring
Orange #FF7A00 --pe-orange Aktionen und Highlights der Marke (Buttons, Abschlagbereich im Logo). Nie als Auswahl, nie als Text auf Creme
Creme #F7F7F2 --pe-creme Seitenhintergrund und helle Flächen
Neu
Planfläche #DCE8E5 --pe-planflaeche Einziger Hintergrund hinter Plänen, Bällen und Diagrammen (Sockel)

Weitere Töne

  • --pe-text-gedaempft Sekundärtext auf hellem Grund
  • --pe-text-auf-petrol Fließtext auf Petrol
  • --pe-weiss Karten auf Creme
  • --pe-linie, --pe-flaeche-sanft abgeleitet (Petrol auf Creme): feine Linien und zurückgenommene Flächen

Planfläche #DCE8E5

  • Was: Ein kühler, leicht petrolgetönter Hellton (Petrol mit etwa 12 % auf Weiß). Er ist der einzige Hintergrund hinter Plänen, Bahn-Thumbnails, Diagrammflächen und Ballbildern.
  • Warum: Er ist neutral genug, dass Anlagenfarben wie Grau, Rot oder Blau unverfälscht wirken. Warmes Creme würde sie einfärben, ein reines Grau hätte keinen Bezug zur Marke und käme mit grauen Bahnen in Konflikt.
  • Wo: Sockel in Karten, Detailansicht, Diagramm und Druckplan. Nicht als Seitenhintergrund und nicht für Text; dafür bleibt Creme.
  • Regel: Jeder Plan hat eine eigene Rand- oder Linienfarbe. So bleibt auch ein graues Bahnfeld auf der Planfläche erkennbar. Im Druck wahlweise 12-%-Raster oder Weiß, die Linien bleiben.

Regeln für Orange

Orange: Aktionen und Highlights der Marke; nie als Auswahl, nie als Text auf Creme.

  • Orange auf Petrol ist erlaubt (ca. 4,7:1), Petrol auf Orange ebenso.
  • Orange als Textfarbe auf Creme oder Weiß ist nicht erlaubt (zu wenig Kontrast).
Orange auf Petrol Petrol auf Orange

Form

Container 1200 px (seitlich 40 px, mobil 16 px) · Radien Karten 28 px, große Flächen 40 px, Buttons und Tags Pille · Rahmen 2–3 px Petrol · Fokus: Petrol-Ring, mind. 3 px.

Code

static/css/tokens.css

/*
 * Design-Tokens „Putt happens“ (siehe /styleguide/).
 * Alle Farben, Radien und Abstände der Seite kommen von hier; Komponenten-CSS verwendet nur var(--pe-…).
 * Neue Hex-Werte gehören in diese Datei, nicht in style.css.
 */

/* Schriften: self-hosted (DSGVO), Dateien per scripts/fonts-holen.sh nach static/fonts/ holen */
@font-face {
  font-family: "Bricolage Grotesque";
  src: url("../fonts/bricolage-grotesque-latin-wght-normal.woff2") format("woff2");
  font-weight: 200 800;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Figtree";
  src: url("../fonts/figtree-latin-wght-normal.woff2") format("woff2");
  font-weight: 300 900;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Markenfarben */
  --pe-petrol: #003B40;             /* Marke, Text, Rahmen, Auswahlring */
  --pe-orange: #FF7A00;             /* Aktionen und Highlights der Marke; nie als Auswahl, nie als Text auf Creme */
  --pe-creme: #F7F7F2;              /* Seitenhintergrund und helle Flächen */
  --pe-planflaeche: #DCE8E5;        /* einziger Hintergrund hinter Plänen, Bällen und Diagrammen (Sockel) */
  --pe-text-gedaempft: #44666A;     /* Sekundärtext auf hellem Grund */
  --pe-text-auf-petrol: #E4EBE9;    /* Fließtext auf Petrol */
  --pe-weiss: #FFFFFF;              /* Karten auf Creme */

  /* Abgeleitete Töne (Petrol auf Creme gemischt): feine Linien und sanfte Flächen */
  --pe-linie: #C5D1CE;              /* Trennlinien, gestrichelte Ränder (Petrol ca. 20 %) */
  --pe-flaeche-sanft: #EBEEE9;      /* zurückgenommene Flächen, z. B. Kartenköpfe (Petrol ca. 5 %) */

  /* Rückfallwerte für Daten */
  --pe-ball-unbekannt: #898781;     /* Ball ohne Farbangabe */
  --pe-fehler: #B3261E;             /* Fehlermeldung (Kontrast auf Creme/Weiß > 6:1) */
  --pe-ok: #1B6E3A;                 /* Erfolgsmeldung */

  /* Schrift */
  --pe-schrift-titel: "Bricolage Grotesque", system-ui, sans-serif;
  --pe-schrift-text: "Figtree", system-ui, sans-serif;
  --pe-schrift-code: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;

  /* Form */
  --pe-radius-karte: 28px;
  --pe-radius-gross: 40px;          /* große Flächen, Hero */
  --pe-radius-innen: 18px;          /* Planfläche in der Plan-Karte */
  --pe-radius-pille: 999px;         /* Buttons und Tags */
  --pe-rahmen: 3px;                 /* Rahmenstärke Karten (2–3 px) */
  --pe-rahmen-fein: 2px;            /* Buttons, Ball-Chips */
  --pe-versatz-orange: 0 10px 0 var(--pe-orange);   /* Akzent-Offset-Schatten für hervorgehobene Karten */

  /* Raster */
  --pe-container: 1200px;
  --pe-rand: 40px;                  /* seitlicher Abstand, mobil 16 px */
  --pe-touch: 44px;                 /* Mindestgröße von Touch-Zielen */
}

@media (max-width: 640px) {
  :root { --pe-rand: 16px; }
}

Typografie

Bricolage Grotesque

Putt happens

Pistenpläne und Bälle

Spielvarianten im Überblick

  • Überschriften und Wortmarke.
  • Gewicht 800 für große Titel, 500–700 darunter.
  • letter-spacing −0,03em bis −0,04em, line-height ca. 0,95–1,1.

Figtree

Fließtext und Oberfläche: Der Ball rollt über die Bahn zum Loch.

400 600 700 800

  • Gewichte 400 / 600 / 700 / 800.
  • Basis 17–18 px, line-height 1,5.
  • Fallback-Stack: system-ui, sans-serif.

Self-hosted: Die Schriften kommen von static/fonts/ (woff2), nicht von Google Fonts (DSGVO). Holen: scripts/fonts-holen.sh.

Code

static/css/tokens.css

/*
 * Design-Tokens „Putt happens“ (siehe /styleguide/).
 * Alle Farben, Radien und Abstände der Seite kommen von hier; Komponenten-CSS verwendet nur var(--pe-…).
 * Neue Hex-Werte gehören in diese Datei, nicht in style.css.
 */

/* Schriften: self-hosted (DSGVO), Dateien per scripts/fonts-holen.sh nach static/fonts/ holen */
@font-face {
  font-family: "Bricolage Grotesque";
  src: url("../fonts/bricolage-grotesque-latin-wght-normal.woff2") format("woff2");
  font-weight: 200 800;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Figtree";
  src: url("../fonts/figtree-latin-wght-normal.woff2") format("woff2");
  font-weight: 300 900;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Markenfarben */
  --pe-petrol: #003B40;             /* Marke, Text, Rahmen, Auswahlring */
  --pe-orange: #FF7A00;             /* Aktionen und Highlights der Marke; nie als Auswahl, nie als Text auf Creme */
  --pe-creme: #F7F7F2;              /* Seitenhintergrund und helle Flächen */
  --pe-planflaeche: #DCE8E5;        /* einziger Hintergrund hinter Plänen, Bällen und Diagrammen (Sockel) */
  --pe-text-gedaempft: #44666A;     /* Sekundärtext auf hellem Grund */
  --pe-text-auf-petrol: #E4EBE9;    /* Fließtext auf Petrol */
  --pe-weiss: #FFFFFF;              /* Karten auf Creme */

  /* Abgeleitete Töne (Petrol auf Creme gemischt): feine Linien und sanfte Flächen */
  --pe-linie: #C5D1CE;              /* Trennlinien, gestrichelte Ränder (Petrol ca. 20 %) */
  --pe-flaeche-sanft: #EBEEE9;      /* zurückgenommene Flächen, z. B. Kartenköpfe (Petrol ca. 5 %) */

  /* Rückfallwerte für Daten */
  --pe-ball-unbekannt: #898781;     /* Ball ohne Farbangabe */
  --pe-fehler: #B3261E;             /* Fehlermeldung (Kontrast auf Creme/Weiß > 6:1) */
  --pe-ok: #1B6E3A;                 /* Erfolgsmeldung */

  /* Schrift */
  --pe-schrift-titel: "Bricolage Grotesque", system-ui, sans-serif;
  --pe-schrift-text: "Figtree", system-ui, sans-serif;
  --pe-schrift-code: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;

  /* Form */
  --pe-radius-karte: 28px;
  --pe-radius-gross: 40px;          /* große Flächen, Hero */
  --pe-radius-innen: 18px;          /* Planfläche in der Plan-Karte */
  --pe-radius-pille: 999px;         /* Buttons und Tags */
  --pe-rahmen: 3px;                 /* Rahmenstärke Karten (2–3 px) */
  --pe-rahmen-fein: 2px;            /* Buttons, Ball-Chips */
  --pe-versatz-orange: 0 10px 0 var(--pe-orange);   /* Akzent-Offset-Schatten für hervorgehobene Karten */

  /* Raster */
  --pe-container: 1200px;
  --pe-rand: 40px;                  /* seitlicher Abstand, mobil 16 px */
  --pe-touch: 44px;                 /* Mindestgröße von Touch-Zielen */
}

@media (max-width: 640px) {
  :root { --pe-rand: 16px; }
}

scripts/fonts-holen.sh

#!/bin/sh
# Holt die self-hosted Schriften (Fontsource, OFL) nach static/fonts/. Aufruf im Projektordner: sh scripts/fonts-holen.sh
# Erwartet werden bricolage-grotesque-latin-wght-normal.woff2 (Überschriften, Wortmarke) und figtree-latin-wght-normal.woff2 (Text).
# Ohne die Dateien greift der Fallback system-ui, sans-serif.
set -e
ziel="$(cd "$(dirname "$0")/.." && pwd)/static/fonts"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
cd "$tmp"
npm init -y >/dev/null
npm install --silent @fontsource-variable/bricolage-grotesque @fontsource-variable/figtree
cp node_modules/@fontsource-variable/bricolage-grotesque/files/bricolage-grotesque-latin-wght-normal.woff2 "$ziel/"
cp node_modules/@fontsource-variable/figtree/files/figtree-latin-wght-normal.woff2 "$ziel/"
echo "Schriften liegen in $ziel"

Komponenten

Buttons und Tags

Tag Tag mit Rahmen

Primär: Pille, Orange, Petrol-Text, fett. Sekundär: Pille, 2 px Rahmen, transparent; auf Petrol in Creme.

Formularfelder

Echte <label> und <input>, Touch-Ziele mind. 44 px, accent-color Orange.

Suchfeld-Karte

Creme, 3 px Petrol-Rahmen, Offset-Schatten in Orange (nur für hervorgehobene Karten).

Plan-Karte mit echtem Plan und Spielvariante

Nettetal - Abteilung 1mit Linie der Spielvariante

Weiße Karte, 3 px Petrol-Rahmen, 28 px Radius; innen die Planfläche (18 px Radius), darunter Farbchip, Titel und Meta in gedämpftem Text. Der Plan ist die echte beton-bahn-<nr>.svg, die Linie das linie-Overlay der Spielvariante in der Ballfarbe (gleiche viewBox).

Ball-Chips

033 087 WMC VFB
A A B B C C

Normal und ausgewählt (Petrol-Ring mit weißem Spalt), auch bei Petrol- und Orange-Ball. Jeder Ball trägt zusätzlich ein Kürzel (Buchstabe), damit er auch bei Farbschwäche und im Schwarzweiß-Druck erkennbar ist. Das Kürzel ist der Anfangsbuchstabe des Ballnamens (nicht eindeutig, aber zusammen mit der Farbe ein guter Hinweis).

Wiki-Kachel

[THEMA]

[Kurzer Anreißertext]

Hero-Ausschnitt

Putt happens

Übersicht aller Minigolf-Bälle mit ihren Eigenschaften

Abschnitt auf Orange (Text Petrol), z. B. „Ballauswahl“.
Code

layouts/partials/button.html

{{- /* Button als Pille. Eingabe (dict):
         text   Beschriftung
         url    optional: dann ein Link (<a>), sonst ein <button>
         art    "primaer" (Orange, Petrol-Text) | "sekundaer" (Standard: 2 px Rahmen, transparent)
                Auf Petrol (Elternelement mit Klasse auf-petrol) wird die sekundäre Variante in Creme gezeichnet.
         typ    optional: type des <button> (Standard "button") */ -}}
{{- $klasse := printf "btn btn-%s" (.art | default "sekundaer") -}}
{{- if .url -}}
<a class="{{ $klasse }}" href="{{ .url }}">{{ .text }}</a>
{{- else -}}
<button class="{{ $klasse }}" type="{{ .typ | default "button" }}">{{ .text }}</button>
{{- end -}}

layouts/partials/plan-karte.html

{{- /* Plan-Karte (Sockel): weiße Karte, 3 px Petrol-Rahmen, 28 px Radius, innen die Planfläche (18 px Radius),
       darunter Farbchip, Titel und Meta. Pläne, Bälle und Diagramme behalten ihre Echtfarben und sitzen
       immer auf der Planfläche. Eingabe (dict):
         inhalt   HTML des Inhalts (Plan, Ballfoto, Diagramm)
         chip     optional: Hex-Wert der Anlagenfarbe (Farbchip vor dem Titel)
         titel    optional: Titel (bei link: Link über die ganze Karte)
         meta     optional: Zusatzzeile in gedämpftem Text
         link     optional: Ziel des Titels
         klasse   optional: weitere CSS-Klassen */ -}}
<figure class="plan-karte{{ with .klasse }} {{ . }}{{ end }}"{{ with .chip }} style="--plan-chip: {{ . }}"{{ end }}>
  <div class="plan-sockel">{{ .inhalt | safeHTML }}</div>
  {{- if or .titel .meta (isset . "chip") }}
  <figcaption>
    {{ if isset . "chip" }}{{ partial "farbchip.html" .chip }}{{ end -}}
    {{ with .titel }}<span class="plan-titel">{{ if $.link }}<a class="plan-link" href="{{ $.link }}">{{ . }}</a>{{ else }}{{ . }}{{ end }}</span>{{ end -}}
    {{ with .meta }}<span class="plan-meta">{{ . }}</span>{{ end }}
  </figcaption>
  {{- end }}
</figure>

layouts/partials/bahn-plan.html

{{- /* Bahnplan in der Plan-Karte (Sockel mit Farbchip der Anlage). Eingabe wie bahn-plan-wrap.html, zusätzlich:
         karte_titel  optional: Titel unter dem Plan (Standard: Name der Anlage)
         karte_meta   optional: Zusatzzeile (Standard: Länge der Bahn, falls angegeben)
         chip         optional: Hex-Wert der Anlagenfarbe (Standard: farben.chip der Anlage, sonst farben.bahn/flaeche)
         ohne_karte   optional (bool): nur den Plan ausgeben, ohne Karte
         klein        optional (bool): kompakte Karte (Übersicht) */ -}}
{{- $inhalt := partial "bahn-plan-wrap.html" . -}}
{{- if .ohne_karte -}}
  {{- $inhalt -}}
{{- else -}}
  {{- $bahn := .bahn -}}
  {{- $chip := .chip | default "" -}}
  {{- if not $chip }}{{ with $bahn.Parent }}{{ $chip = partial "anlage-chip.html" . }}{{ end }}{{ end -}}
  {{- with $bahn.Params.farben }}{{ with partial "anlage-chip.html" $bahn }}{{ $chip = . }}{{ end }}{{ end -}}
  {{- $titel := .karte_titel | default $bahn.Parent.Title -}}
  {{- $meta := .karte_meta | default "" -}}
  {{- if and (not $meta) $bahn.Params.laenge }}{{ $meta = printf "Länge %v m" $bahn.Params.laenge }}{{ end -}}
  {{- partial "plan-karte.html" (dict "inhalt" $inhalt "chip" $chip "titel" $titel "meta" $meta "klasse" (cond (.klein | default false) "plan-karte-klein" "")) -}}
{{- end -}}

layouts/partials/farbchip.html

{{- /* Farbchip: Anlagenfarbe als kleiner runder Chip (22 px, 3 px Petrol-Rand). Siehe .farbchip in style.css.
       Eingabe: Hex-Wert (#RRGGBB) als String oder dict mit farbe (Hex) und optional titel (Tooltip).
       Ohne gültigen Hex-Wert erscheint ein leerer Chip in Creme. Der Chip ist dekorativ (aria-hidden):
       der Anlagenname steht immer als Text daneben. */ -}}
{{- $farbe := . -}}
{{- $titel := "" -}}
{{- if reflect.IsMap . }}{{ $farbe = .farbe }}{{ $titel = .titel | default "" }}{{ end -}}
{{- $farbe = string ($farbe | default "") -}}
<span class="farbchip" aria-hidden="true"{{ if findRE `^#[0-9A-Fa-f]{3,8}$` $farbe }} style="--chip: {{ $farbe }}"{{ end }}{{ with $titel }} title="{{ . }}"{{ end }}></span>

layouts/partials/ball-chip.html

{{- /* Ball-Chip: Kreis in Ballfarbe, 2 px Petrol-Rand, Buchstabe zentriert. Zustand „ausgewählt“: Petrol-Ring
       mit weißem Spalt (nie Orange). Eingabe (dict):
         ball          Seite des Balls (liefert Farbe, Kürzel, Name) – oder statt dessen:
         farbe         Hex-Wert    und    kuerzel   Text     (für Beispiele)
         ausgewaehlt   optional (bool)
         groesse       optional: Durchmesser in px (Standard 34)
         name          optional: Name für Screenreader (bei ball: Titel des Balls) */ -}}
{{- $farbe := .farbe | default "" -}}
{{- $kuerzel := .kuerzel | default "" -}}
{{- $name := .name | default "" -}}
{{- with .ball -}}
  {{- $farbe = .Params.farbe | default $farbe -}}
  {{- $kuerzel = partialCached "ball-kuerzel.html" . .File.ContentBaseName -}}
  {{- $name = .Title -}}
{{- end -}}
{{- $farbe = $farbe | default "#898781" -}}
{{- $text := cond (eq (partial "farb-kontrast.html" $farbe) "petrol") "var(--pe-petrol)" "var(--pe-weiss)" -}}
<span class="ball-chip{{ if .ausgewaehlt }} ausgewaehlt{{ end }}" data-len="{{ len $kuerzel }}" style="--ball: {{ $farbe }}; --ball-text: {{ $text | safeCSS }};{{ with .groesse }} --ball-groesse: {{ . }}px;{{ end }}"
  {{- if $name }} role="img" aria-label="{{ $name }}{{ if .ausgewaehlt }}, ausgewählt{{ end }}"{{ else }} aria-hidden="true"{{ end }}>{{ $kuerzel }}</span>

layouts/partials/ball-kuerzel.html

{{- /* Kürzel eines Balls für Ball-Chip und Streudiagramm: Der Ball ist nie nur über seine Farbe erkennbar.
       Eingabe: Seite des Balls. Gilt das Feld kuerzel (Front Matter, höchstens 3 Zeichen), sonst der
       Anfangsbuchstabe des Balltitels in Großschreibung (bei Zahlen die erste Ziffer). Die Kürzel sind nicht
       eindeutig; zusammen mit der Farbe geben sie einen guten Hinweis, und der volle Name steht im Tooltip.
       Rückgabe: String. */ -}}
{{- $kuerzel := "" -}}
{{- with .Params.kuerzel }}{{ $kuerzel = string . }}{{ end -}}
{{- if not $kuerzel }}{{ $kuerzel = upper (substr .Title 0 1) }}{{ end -}}
{{- return $kuerzel -}}

layouts/partials/farb-kontrast.html

{{- /* Lesbare Schriftfarbe auf einer Ballfarbe. Eingabe: Hex-Wert (#RGB oder #RRGGBB). Rückgabe: "weiss" oder
       "petrol" (Namen der Tokens --pe-weiss / --pe-petrol). Gleiche Rechnung wie kontrastFarbe() in
       static/js/ballauswahl.js (Helligkeit 0,299 R + 0,587 G + 0,114 B, Grenze 0,6). */ -}}
{{- $h := string (. | default "") -}}
{{- $erg := "weiss" -}}
{{- if findRE `^#[0-9A-Fa-f]{3}$` $h -}}
  {{- $h = printf "#%s%s%s%s%s%s" (substr $h 1 1) (substr $h 1 1) (substr $h 2 1) (substr $h 2 1) (substr $h 3 1) (substr $h 3 1) -}}
{{- end -}}
{{- if findRE `^#[0-9A-Fa-f]{6}$` $h -}}
  {{- $r := int (printf "0x%s" (substr $h 1 2)) -}}
  {{- $g := int (printf "0x%s" (substr $h 3 2)) -}}
  {{- $b := int (printf "0x%s" (substr $h 5 2)) -}}
  {{- $y := div (add (add (mul 299 $r) (mul 587 $g)) (mul 114 $b)) 255000.0 -}}
  {{- if gt $y 0.6 }}{{ $erg = "petrol" }}{{ end -}}
{{- end -}}
{{- return $erg -}}

layouts/partials/kachel.html

{{- /* Wiki-Kachel: Petrol mit Cremetext und orangem „Lesen →“. Eingabe (dict): titel, text, url */ -}}
<a class="kachel" href="{{ .url }}">
  <h3>{{ .titel }}</h3>
  <p>{{ .text }}</p>
  <span class="kachel-lesen" aria-hidden="true">Lesen →</span>
</a>

static/css/style.css

/*
 * Komponenten-CSS „Putt happens“. Farben, Radien, Schriften: nur über die Tokens aus tokens.css.
 * Aufbau: Basis · Kopf/Fuß · Startseite · Komponenten · Seiten (Ball, Bahn, …) · Druck
 */

:root {
  font-family: var(--pe-schrift-text);
  color: var(--pe-petrol);
  font-size: 17px;
  line-height: 1.5;
  --inhalt-breite: var(--pe-container);   /* maximale Breite des Inhaltsbereichs */
  --kopf-hoehe: 4.5rem;                   /* Höhe der Kopfzeile (auch Basis für die Bahn-Vollbildansicht) */
  --seitenleiste: 15rem;                  /* Breite der Seitenleiste auf der Bahn-Einzelseite */
}

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

body {
  margin: 0;
  background: var(--pe-creme);
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

h1, h2, h3 {
  font-family: var(--pe-schrift-titel);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.05;
}
h1 { font-weight: 800; letter-spacing: -0.04em; font-size: clamp(2rem, 5vw, 3.25rem); margin: 0 0 0.5em; }
h2 { font-size: clamp(1.4rem, 3vw, 2rem); }
h3 { font-weight: 600; }

a { color: var(--pe-petrol); text-underline-offset: 0.15em; }

/* Fokus: gut sichtbarer Petrol-Ring (mind. 3 px) für alle interaktiven Elemente */
:focus-visible {
  outline: 3px solid var(--pe-petrol);
  outline-offset: 3px;
}
.auf-petrol :focus-visible { outline-color: var(--pe-creme); }

.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

/* ---------- Kopf und Fuß ---------- */

.site-header {
  height: var(--kopf-hoehe);
  padding: 0 var(--pe-rand);
  border-bottom: var(--pe-rahmen-fein) solid var(--pe-petrol);
  background: var(--pe-creme);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
}

.logo {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  color: var(--pe-petrol);
  text-decoration: none;
}
.logo-mark { display: block; height: var(--logo-hoehe, 44px); width: auto; flex: none; }
.logo-wort {
  font-family: var(--pe-schrift-titel);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1;
  font-size: calc(var(--logo-hoehe, 44px) * 0.55);
  white-space: nowrap;
}
.logo.auf-petrol { color: var(--pe-creme); }

.site-nav { display: flex; flex-wrap: wrap; gap: 0 0.25rem; }
.site-nav a {
  display: inline-flex;
  align-items: center;
  min-height: var(--pe-touch);
  padding: 0 0.75rem;
  border-radius: var(--pe-radius-pille);
  color: inherit;
  font-weight: 700;
  text-decoration: none;
}
.site-nav a:hover { background: var(--pe-planflaeche); }
.site-nav a[aria-current="page"] { box-shadow: inset 0 -3px 0 var(--pe-petrol); border-radius: 0; }

main {
  box-sizing: border-box;
  width: 100%;
  max-width: calc(var(--inhalt-breite) + 2 * var(--pe-rand));
  margin: 0 auto;
  padding: 1.5rem var(--pe-rand);
  flex: 1;
}

.site-footer {
  margin-top: 3rem;
  padding: 2.5rem var(--pe-rand);
  background: var(--pe-petrol);
  color: var(--pe-text-auf-petrol);
  border-radius: var(--pe-radius-gross) var(--pe-radius-gross) 0 0;
}
.site-footer-innen {
  max-width: var(--pe-container);
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem 3rem;
  justify-content: space-between;
  align-items: flex-start;
}
.site-footer a { color: var(--pe-creme); }
.site-footer nav { display: flex; flex-wrap: wrap; gap: 0.25rem 1.25rem; }
.site-footer nav a { display: inline-flex; align-items: center; min-height: var(--pe-touch); font-weight: 600; }
.site-footer p { margin: 0.5rem 0 0; max-width: 32rem; }

/* ---------- Startseite ---------- */

.hero {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: 2rem;
  align-items: center;
  padding: clamp(1.5rem, 4vw, 3.5rem);
  border-radius: var(--pe-radius-gross);
  background: var(--pe-petrol);
  color: var(--pe-text-auf-petrol);
}
.hero h1 { color: var(--pe-creme); line-height: 0.95; }
.hero p { font-size: 1.15rem; max-width: 34rem; }
.hero-aktionen { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 1.5rem; }

.abschnitt { margin-top: 3rem; }
.abschnitt > h2 { margin: 0 0 1rem; }
.abschnitt-orange {
  padding: clamp(1.5rem, 4vw, 3rem);
  border-radius: var(--pe-radius-gross);
  background: var(--pe-orange);
  color: var(--pe-petrol);
}
.abschnitt-orange h2 { margin-top: 0; }
.ball-reihe { display: flex; flex-wrap: wrap; gap: 0.6rem; margin: 1rem 0; }
.hero-bahn { max-width: 22rem; justify-self: center; width: 100%; }
.hero-bahn .plan-karte { background: var(--pe-creme); }
.spielvarianten-teaser { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 24rem); gap: 1.5rem; align-items: start; }
.spielvarianten-teaser p { margin: 0; max-width: 34rem; }
.chip-name { display: inline-flex; align-items: center; gap: 0.6rem; }
h1.chip-name { display: flex; }
h1.chip-name .farbchip { width: 28px; height: 28px; border-width: 4px; }
.anlage-zeile { margin: 0; font-weight: 700; }
.anlage-intro { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem 1rem; }
.anlage-intro > h1, .anlage-intro > div { flex: 1 1 100%; margin: 0; }
.druckkopf, .druckfuss { display: none; }
.abschnitt-orange p { max-width: 38rem; }

.karten-raster {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
  gap: 1.5rem;
}

.home-links {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  margin-top: 1.5rem;
}
.home-card {
  display: block;
  padding: 1.25rem;
  border: var(--pe-rahmen) solid var(--pe-petrol);
  border-radius: var(--pe-radius-karte);
  background: var(--pe-weiss);
  text-decoration: none;
  color: inherit;
}
.home-card:hover { box-shadow: 0 6px 0 var(--pe-petrol); }
.home-card h2 { margin: 0 0 6px; }
.home-card p { margin: 0; color: var(--pe-text-gedaempft); }

/* ---------- Komponenten ---------- */

/* Buttons: Pille; Standard = sekundär, .btn-primaer = Orange mit Petrol-Text */
button, .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: var(--pe-touch);
  padding: 0 1.25rem;
  border: var(--pe-rahmen-fein) solid var(--pe-petrol);
  border-radius: var(--pe-radius-pille);
  background: transparent;
  color: var(--pe-petrol);
  font: inherit;
  font-weight: 700;
  line-height: 1.1;
  text-decoration: none;
  cursor: pointer;
}
button:hover, .btn:hover { background: var(--pe-planflaeche); }
button:disabled { opacity: 0.5; cursor: default; }
.btn-primaer { background: var(--pe-orange); border-color: var(--pe-orange); color: var(--pe-petrol); }
.btn-primaer:hover { background: var(--pe-orange); box-shadow: 0 4px 0 var(--pe-petrol); }
.auf-petrol .btn-sekundaer { border-color: var(--pe-creme); color: var(--pe-creme); }
.auf-petrol .btn-sekundaer:hover { background: var(--pe-creme); color: var(--pe-petrol); }

/* Tag: kleine Pille */
.tag {
  display: inline-flex;
  align-items: center;
  padding: 0.1rem 0.75rem;
  border: var(--pe-rahmen-fein) solid var(--pe-petrol);
  border-radius: var(--pe-radius-pille);
  background: var(--pe-petrol);
  color: var(--pe-creme);
  font-size: 0.85rem;
  font-weight: 700;
}
.tag-rahmen { background: transparent; color: var(--pe-petrol); }

/* Formulare: echte label/input, Touch-Ziele mind. 44 px */
input, select, textarea {
  font: inherit;
  color: var(--pe-petrol);
  background: var(--pe-weiss);
  border: var(--pe-rahmen-fein) solid var(--pe-petrol);
  border-radius: 12px;
  min-height: var(--pe-touch);
  padding: 0 0.75rem;
}
textarea { padding: 0.5rem 0.75rem; }
input[type="checkbox"], input[type="radio"] {
  accent-color: var(--pe-orange);
  width: 1.4rem;
  height: 1.4rem;
  min-height: 0;
  padding: 0;
}

/* Suchfeld-Karte: Creme, Petrol-Rahmen, Offset-Schatten in Orange */
.suchkarte {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  padding: 1rem 1.25rem;
  margin-bottom: 10px;
  border: var(--pe-rahmen) solid var(--pe-petrol);
  border-radius: var(--pe-radius-karte);
  background: var(--pe-creme);
  box-shadow: var(--pe-versatz-orange);
}
.suchkarte label { font-weight: 700; }
.suchkarte input { flex: 1 1 14rem; }

/* Wiki-Kachel: Petrol mit Cremetext und orangem „Lesen →“ (Orange nur auf Petrol) */
.kachel {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 1.5rem;
  border-radius: var(--pe-radius-karte);
  background: var(--pe-petrol);
  color: var(--pe-text-auf-petrol);
  text-decoration: none;
}
.kachel h3 { margin: 0; color: var(--pe-creme); font-size: 1.4rem; }
.kachel p { margin: 0; }
.kachel-lesen { margin-top: auto; color: var(--pe-orange); font-weight: 800; }

/* Plan-Karte (Sockel): weiße Karte, Petrol-Rahmen, innen die Planfläche. Pläne, Bälle und Diagramme
   behalten ihre Echtfarben und sitzen immer auf var(--pe-planflaeche). */
.plan-karte {
  margin: 0;
  padding: 10px;
  border: var(--pe-rahmen) solid var(--pe-petrol);
  border-radius: var(--pe-radius-karte);
  background: var(--pe-weiss);
  color: var(--pe-petrol);
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}
.plan-karte > a { display: block; color: inherit; text-decoration: none; }
.plan-sockel {
  padding: 12px;
  border-radius: var(--pe-radius-innen);
  background: var(--pe-planflaeche);
}
.plan-karte figcaption {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.6rem;
  padding: 0.6rem 0.5rem 0.3rem;
}
.plan-titel { font-family: var(--pe-schrift-titel); font-weight: 700; letter-spacing: -0.02em; }
.plan-meta { color: var(--pe-text-gedaempft); font-size: 0.9rem; }
/* Karte als Ganzes klickbar: der Link im Titel wird über die Karte gezogen */
.plan-karte:has(.plan-link) { position: relative; }
.plan-karte:has(.plan-link):hover { box-shadow: 0 6px 0 var(--pe-petrol); }
.plan-link { color: inherit; text-decoration: none; }
.plan-link::after { content: ""; position: absolute; inset: 0; border-radius: var(--pe-radius-karte); }
.plan-link:focus-visible { outline: none; }
.plan-link:focus-visible::after { outline: 3px solid var(--pe-petrol); outline-offset: 3px; }
.plan-karte-klein { padding: 8px; }
.plan-karte-klein .plan-sockel { padding: 10px; }
.plan-karte-klein .bahn-plan-wrap { max-width: 14rem; margin-inline: auto; }
.plan-karte-diagramm { max-width: 760px; margin-inline: auto; }
.plan-karte-diagramm .ball-chart, .plan-karte-legende .ball-chart { max-width: none; }
/* Seite Bälle: Diagramm links, Legende als eigene Karte daneben (schmal: untereinander) */
.diagramm-mit-legende { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 23rem); gap: 1.5rem; align-items: start; }
.diagramm-mit-legende .plan-karte-diagramm { max-width: none; width: 100%; margin: 0; }
.plan-karte-legende { margin: 0; }
@media (max-width: 640px) { .diagramm-mit-legende { grid-template-columns: minmax(0, 1fr); } }

/* Farbchip: Anlagenfarbe als kleiner runder Chip mit Petrol-Rand */
.farbchip {
  display: inline-block;
  flex: none;
  width: 22px;
  height: 22px;
  border: 3px solid var(--pe-petrol);
  border-radius: 50%;
  background: var(--chip, var(--pe-creme));
  vertical-align: middle;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}
.chip-name { display: inline-flex; align-items: center; gap: 0.5rem; }

/* Ball-Chip: Kreis in Ballfarbe mit Petrol-Rand und Buchstabe. Auswahl = Petrol-Ring mit weißem Spalt
   (funktioniert auch bei petrolfarbenem Ball); das Marken-Orange wird nie als Auswahl verwendet. */
.ball-chip {
  --ball: var(--pe-ball-unbekannt);
  --ball-text: var(--pe-weiss);
  --ball-groesse: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: var(--ball-groesse);
  height: var(--ball-groesse);
  border: var(--pe-rahmen-fein) solid var(--pe-petrol);
  border-radius: 50%;
  background: var(--ball);
  color: var(--ball-text);
  font-family: var(--pe-schrift-text);
  font-weight: 800;
  font-size: calc(var(--ball-groesse) * 0.42);
  line-height: 1;
  letter-spacing: 0;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}
/* Längere Kürzel (2–3 Zeichen) werden kleiner gesetzt, damit sie in den Kreis passen */
.ball-chip[data-len="2"] { font-size: calc(var(--ball-groesse) * 0.36); letter-spacing: -0.02em; }
.ball-chip[data-len="3"] { font-size: calc(var(--ball-groesse) * 0.28); letter-spacing: -0.04em; }
.ball-chip.ausgewaehlt { box-shadow: 0 0 0 3px var(--pe-weiss), 0 0 0 7px var(--pe-petrol); }

/* Auswahl-Ring im Streudiagramm (Hover, Fokus) */
.ball-chart .ring { display: none; fill: none; }
.ball-chart .ring-spalt { stroke: var(--pe-weiss); stroke-width: 3; }
.ball-chart .ring-aussen { stroke: var(--pe-petrol); stroke-width: 3.5; }
.ball-chart a:hover .ring, .ball-chart a:focus-visible .ring { display: inline; }
.ball-chart a:focus-visible { outline: none; }
.ball-chart .ball-buchstabe { font-weight: 800; text-anchor: middle; dominant-baseline: central; pointer-events: none; }

/* Übersichts-Diagramm */
.ball-chart {
  display: block;
  width: 100%;
  max-width: 720px;                 /* Hochformat-Diagramm: bei breitem Inhaltsbereich nicht mitwachsen */
  height: auto;
  margin: 0 auto;
}

.ball-chart a { cursor: pointer; }
.ball-chart circle { transition: opacity 0.15s; }
.ball-chart a:hover circle { opacity: 0.75; }
.ball-chart text { fill: var(--pe-text-gedaempft); font-size: 12px; font-family: var(--pe-schrift-text); }
.ball-chart .axis-label { font-size: 14px; font-weight: 500; fill: var(--pe-petrol); }

/* Einzelseite */
.ball-detail { display: flex; flex-direction: column; gap: 1.5rem; }
/* Foto links, Werte rechts daneben (Farbbalken direkt am Bild); bei schmalem Fenster untereinander */
.ball-kopf { display: flex; flex-wrap: wrap; align-items: stretch; gap: 1rem; }
.ball-foto { flex: 0 0 auto; width: 100%; max-width: 300px; }
.ball-foto .plan-sockel { padding: 10px; }
.ball-detail .ball-foto img {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  border-radius: 12px;
  object-fit: cover;
}
.ball-titel { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }
.ball-titel h1 { margin: 0; }
.ball-titel .ball-chip { --ball-groesse: 56px; }
.ball-stats { flex: 1 1 260px; max-width: 28rem; list-style: none; padding: 1rem 1.25rem; margin: 0; display: grid; align-content: center; gap: 10px; border: var(--pe-rahmen) solid var(--pe-petrol); border-left: 10px solid var(--ball, var(--pe-petrol)); border-radius: var(--pe-radius-karte); background: var(--pe-weiss); -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.ball-stats li { display: flex; justify-content: space-between; border-bottom: 1px solid var(--pe-linie); padding-bottom: 6px; }
.ball-stats .label { color: var(--pe-text-gedaempft); }
.back-link { display: inline-flex; align-items: center; min-height: var(--pe-touch); margin-top: 1rem; font-weight: 700; }

/* Anlagen/Bahnen-Liste */
.plain-list { list-style: none; padding: 0; display: grid; gap: 8px; }
.plain-list li { border-bottom: 1px solid var(--pe-linie); padding-bottom: 6px; }
.muted { color: var(--pe-text-gedaempft); }

/* Bahn-Detailseite */
.bahn-detail { display: flex; flex-direction: column; gap: 1.25rem; }

.bahn-detail h1 { font-size: clamp(1.75rem, 3.5vw, 2.4rem); }
.bahn-detail h1 a {
  color: inherit;
  text-decoration: none;
}
.bahn-detail h1 a:hover {
  text-decoration: underline;
}


.bahn-layout {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);   /* Tabelle links, Grafik rechts (Drittel); minmax(0, …): lange Variantentexte dürfen den Plan nicht verdrängen */
  gap: 1.5rem;
  align-items: start;
}

.bahn-plan-wrap {
  /* Kurzformen der Anlagenfarben (Front Matter farben.bahn / farben.linie, siehe bahn-plan-wrap.html):
     ohne gesetzte Variable bleibt der Wert ungültig, und die SVG-Datei nimmt ihren Normwert als Rückfall.
     Ausdrücklich gesetzte Rollen (inline am Element) haben Vorrang. */
  --plan-flaeche: var(--plan-bahn);
  --plan-bande: var(--plan-linie);
  --plan-grenze: var(--plan-linie);
  --plan-ablege: var(--plan-linie);
  --plan-hilfslinie: var(--plan-linie);
  position: relative;
  width: 100%;
  aspect-ratio: 61 / 94;            /* Format des rechten Drittels im Druck */
}

.bahn-plan {
  width: 100%;
  border: 1px solid transparent;    /* Rahmen liefert die Plan-Karte; die 1 px bleiben für die Ausrichtung des Linien-Overlays */
  border-radius: 8px;
}

.bahn-plan {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center top;      /* alle Bahnen oben bündig */
}

/* Spiegelbildliche Bahn (Front-Matter-Feld gespiegelt): Plan und Linien-Overlay werden gemeinsam
   gespiegelt, die Koordinaten der Linien bleiben die des ungespiegelten Plans. */
.bahn-plan-wrap.gespiegelt { transform: scaleX(-1); }

/* Linien-Overlay: liegt deckungsgleich über dem Bahnplan (gleiche viewBox, gleiche
   Ausrichtung wie .bahn-plan; 1px Versatz wegen des Rahmens des Bildes) */
.bahn-linien {
  position: absolute;
  top: 1px;
  left: 1px;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.bahn-linie {
  fill: none;
  stroke-width: 3;                  /* viewBox-Einheiten = cm */
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.9;
}

.bahn-length { color: var(--pe-text-gedaempft); margin: 0; }

/* Spielvarianten als Karten */
.spielvarianten {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.variante-karte {
  --ball: var(--pe-ball-unbekannt);                      /* Ballfarbe, wird inline pro Karte gesetzt */
  background: var(--pe-weiss);
  border: var(--pe-rahmen-fein) solid var(--pe-petrol);
  border-left: 8px solid var(--ball);   /* schmaler Streifen in der Ballfarbe */
  border-radius: 14px;
  overflow: hidden;
  font-size: 12px;
  break-inside: avoid;
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

.karte-kopf {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  padding: 5px 10px;
  background: var(--pe-flaeche-sanft);                  /* Fallback ohne color-mix */
  background: color-mix(in srgb, var(--ball) 14%, var(--pe-weiss));
}

.karte-titel {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
}
.karte-titel a { color: inherit; text-decoration: none; }
.karte-titel a:hover { text-decoration: underline; }

.karte-wertung {
  flex: none;
  font-size: 11px;
  color: var(--pe-text-gedaempft);
  white-space: nowrap;
}

.karte-koerper {
  padding: 6px 10px 8px;
  display: grid;
  gap: 5px;
}
.karte-koerper p { margin: 0; }

.karte-ball a { color: inherit; font-weight: 500; }
.ball-werte { color: var(--pe-text-gedaempft); margin-left: 4px; }

.karte-chips {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
}
.karte-chips li {
  padding: 1px 8px;
  border: 1px solid var(--pe-linie);
  border-radius: var(--pe-radius-pille);
  white-space: normal;              /* lange Texte (z.B. Anspielpunkt) umbrechen statt die Spalte zu sprengen */
  max-width: 100%;
}

.karte-koerper .lbl { color: var(--pe-text-gedaempft); font-size: 10px; margin-right: 2px; }

.karte-hinweis { font-style: italic; color: var(--pe-text-gedaempft); }
.karte-alternativen { color: var(--pe-text-gedaempft); }

/* Zustände (werden in Schritt 6/7 per Auswahllogik gesetzt) */
.variante-karte.ausprobieren {
  border-color: var(--pe-linie);
  border-left-color: var(--pe-linie);
  font-size: 11px;
  color: var(--pe-text-gedaempft);
}
.variante-karte.ausprobieren .karte-kopf { background: var(--pe-flaeche-sanft); }
.variante-karte.nicht-verfuegbar {
  border-style: dashed;
  border-left-style: dashed;
  border-left-color: var(--pe-linie);
  color: var(--pe-text-gedaempft);
}
.variante-karte.nicht-verfuegbar .karte-kopf { background: var(--pe-flaeche-sanft); }

/* Variantenseite */
.variante-detail { display: flex; flex-direction: column; gap: 1rem; }
.variante-detail h1 { margin: 0; }
.variante-detail .alternativen-titel { margin: 0.5rem 0 0; font-size: 16px; }
.variante-detail .muted { margin: 0; }

/* Ballauswahl: Statuszeile in der Seitenleiste, Auswahlliste auf der Seite "Meine Bälle" */
.ballauswahl-kurz {
  border: var(--pe-rahmen-fein) solid var(--pe-petrol);
  border-radius: var(--pe-radius-innen);
  background: var(--pe-weiss);
  padding: 6px 12px;
}
.ballauswahl-kurz p { margin: 0.25rem 0; }
.ballauswahl-links { color: var(--pe-text-gedaempft); }

.ballauswahl-status { margin: 0 0 6px; color: var(--pe-text-gedaempft); }
.ballauswahl-steuerung { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.ballauswahl-steuerung button { padding: 0 0.9rem; }
.ballauswahl-linkzeile { margin: 0 0 8px; }
.ballauswahl-linkfeld { width: 100%; font-size: 12px; }

.ballauswahl-liste { list-style: none; margin: 0; padding: 0; }
.ballauswahl-liste li { border-bottom: 1px solid var(--pe-linie); }
.ballauswahl-zeile {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 2px;
  cursor: pointer;
}
.ballauswahl-zeile:hover { background: var(--pe-flaeche-sanft); }
.ballauswahl-zeile a { color: inherit; font-weight: 500; }
.ballauswahl-zeile .ball-chip { --ball-groesse: 26px; margin: 4px 4px 4px 6px; }
.ball-punkt {
  --ball: var(--pe-ball-unbekannt);
  flex: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--ball);
  border: 1px solid var(--pe-petrol);
}
.ballauswahl-zeile .ball-werte { margin-left: 0; font-size: 12px; }
.ballauswahl-zeile .muted { margin-left: auto; font-size: 12px; }

.ballauswahl-hinweis {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  margin-bottom: 1rem;
  border: var(--pe-rahmen-fein) solid var(--pe-petrol);
  border-radius: var(--pe-radius-innen);
  background: var(--pe-flaeche-sanft);
}


/* Anlagenseite am Bildschirm: jede Bahn füllt den sichtbaren Bereich unterhalb der Kopfzeile.
   Der Bahnplan wird so skaliert, dass er samt Titel in diese Höhe passt; bei vielen Varianten
   darf die Bahn höher werden (min-height statt height). Der Druck bleibt unverändert (siehe @media print). */
@media screen {
  body:has(.bahn-folge) .site-header {
    position: sticky;
    top: 0;
    z-index: 10;
  }

  html:has(.bahn-folge) {
    scroll-padding-top: var(--kopf-hoehe);
    scroll-snap-type: y proximity;      /* rastet nur in Nähe einer Bahn ein, freies Scrollen bleibt möglich */
  }

  .bahn-folge .bahn-detail {
    box-sizing: border-box;
    min-height: calc(100vh - var(--kopf-hoehe));
    min-height: calc(100dvh - var(--kopf-hoehe));
    padding: 1rem 0 1.5rem;
    border-bottom: 1px solid var(--pe-linie);
    scroll-snap-align: start;
  }

  .bahn-folge .bahn-detail h1 { margin: 0; }

  /* Die Einleitung der Anlage ist selbst ein Einrastpunkt, sonst zieht der Snap beim Laden
     direkt zur ersten Bahn und die Einleitung ist nicht mehr erreichbar */
  .seite-inhalt > .anlage-intro {
    scroll-snap-align: start;
    scroll-margin-top: 5rem;            /* Einrastpunkt liegt ganz oben, der Titel wird nicht angeschnitten */
  }

  .bahn-folge .bahn-layout {
    flex: 1;                            /* Raster füllt den Rest der Bahnhöhe */
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  }

  /* Plan: Höhe = Bildschirmhöhe abzüglich Kopfzeile, Titel, Plan-Karte (Rahmen, Sockel, Bildunterschrift) und Abständen; Breite folgt aus dem
     Seitenverhältnis 61:94, höchstens aber die Spaltenbreite */
  .bahn-folge .bahn-plan-wrap {
    --plan-hoehe: calc(100vh - var(--kopf-hoehe) - 12rem);
    --plan-hoehe: calc(100dvh - var(--kopf-hoehe) - 12rem);
    width: min(100%, calc(var(--plan-hoehe) * 61 / 94));
    min-width: 8rem;
    margin-inline: auto;
  }

  /* Bahn-Einzelseite: dieselbe Bahnansicht wie auf der Anlagenseite, dazu rechts eine Seitenleiste
     mit Ballauswahl, Filter/Sortierung und Rücklink. Die Bahn behält die Inhaltsbreite; die
     Seitenleiste kommt als zusätzliche Spalte dazu. */
  main:has(.bahn-seite) {
    max-width: calc(var(--inhalt-breite) + var(--seitenleiste) + 1.5rem + 2 * var(--pe-rand));
  }

  .bahn-seite {
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--seitenleiste);
    gap: 1.5rem;
    align-items: start;
  }

  .seite-inhalt { min-width: 0; }

  /* Packliste: schmaler Text, Seitenleiste direkt daneben statt am äußersten Rand */
  .bahn-seite.schmal {
    grid-template-columns: minmax(0, 52rem) var(--seitenleiste);
    justify-content: center;
  }

  .bahn-seitenleiste {
    position: sticky;
    top: calc(var(--kopf-hoehe) + 1rem);
    max-height: calc(100vh - var(--kopf-hoehe) - 2rem);
    max-height: calc(100dvh - var(--kopf-hoehe) - 2rem);
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    padding-top: 1rem;                  /* auf einer Linie mit dem Bahntitel */
  }
  .bahn-seitenleiste .ballauswahl-kurz { font-size: 13px; }
  .bahn-seitenleiste .bahnfilter {
    flex-direction: column;
    gap: 0.6rem;
    margin: 0;
  }
  .bahn-seitenleiste .bahnfilter-feld {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    font-size: 13px;
  }
  .bahn-seitenleiste .bahnfilter select { width: 100%; }
  .bahn-seitenleiste .back-link { margin-top: 0; }

  /* Schmale Fenster: Seitenleiste über der Bahn statt daneben */
  @media (max-width: 900px) {
    html:has(.bahn-folge) { scroll-snap-type: none; }   /* Seitenleiste liegt oberhalb, Snap würde sie wegziehen */
    .bahn-seite,
    .bahn-seite.schmal { grid-template-columns: minmax(0, 1fr); }
    .bahn-seitenleiste {
      order: -1;
      position: static;
      max-height: none;
      overflow: visible;
      padding-top: 0;
    }
  }
}

@page {
  size: A4 portrait;
  margin: 0;
}

@media print {
  .site-header,
  .no-print { display: none; }

  main { max-width: none; padding: 0; }

  .bahn-detail, .anlage-intro { position: relative; }

  /* Druckkopf (Marke und Anlagenchip) und Fußzeile liegen nur als Petrol-Linien im Schnittrand der Bahn
     bzw. Anlagenseite; keine gefüllten Flächen (Tintenersparnis). Ein Block ist eine halbe A4-Seite
     (148,5 mm), die nach dem Druck zu A5 quer geschnitten wird. Ränder: 5 mm unbedruckbar, 10 mm seitlich. */
  .druckkopf {
    display: flex;
    position: absolute;
    top: 5mm; left: 10mm; right: 10mm;
    height: 6mm;
    align-items: center;
    gap: 2mm;
    padding-bottom: 0.8mm;
    border-bottom: 0.4mm solid var(--pe-petrol);
    color: var(--pe-petrol);
    font-size: 8pt;
  }
  .druckkopf .logo-mark { --logo-hoehe: 5mm; }
  .druckkopf-wort { font-family: var(--pe-schrift-titel); font-weight: 800; letter-spacing: -0.03em; font-size: 10pt; }
  .druckkopf-anlage { margin-left: auto; display: inline-flex; align-items: center; gap: 2mm; font-weight: 700; }
  .druckkopf .farbchip { width: 4mm; height: 4mm; border: 0.5mm solid var(--pe-petrol); }
  .druckfuss {
    display: block;
    position: absolute;
    left: 10mm; right: 10mm; bottom: 6mm;
    padding-top: 1mm;
    border-top: 0.4mm solid var(--pe-petrol);
    font-size: 7pt;
    color: var(--pe-petrol);
  }

  /* Schnittmarken: kurze graue Striche links und rechts genau auf halber Seitenhöhe (148,5 mm),
     am unteren Rand der oberen Hälfte. Die obere Hälfte ist daher 148,5 mm, die untere 148 mm hoch. */
  .anlage-intro.mit-packliste,
  article.bahn-detail:nth-of-type(odd) { height: 148.5mm; }
  .anlage-intro.mit-packliste::before, .anlage-intro.mit-packliste::after,
  article.bahn-detail:nth-of-type(odd)::before, article.bahn-detail:nth-of-type(odd)::after {
    content: "";
    position: absolute;
    bottom: 0;
    width: 5mm;
    border-top: 0.3mm solid var(--pe-text-gedaempft);
  }
  .anlage-intro.mit-packliste::before, article.bahn-detail:nth-of-type(odd)::before { left: 4mm; }
  .anlage-intro.mit-packliste::after, article.bahn-detail:nth-of-type(odd)::after { right: 4mm; }

  /* Plan-Karte im Druck: Planfläche als Weiß (spart Toner), die Linien und Echtfarben des Plans bleiben;
     der Anlagenchip steht im Druckkopf, die Bildunterschrift entfällt */
  .plan-karte { padding: 1.2mm; border-width: 0.6mm; border-radius: 4mm; }
  .plan-sockel { padding: 1.5mm; border-radius: 3mm; background: var(--pe-weiss); }
  .bahn-detail .plan-karte figcaption { display: none; }

  .anlage-intro {
    height: 296mm;
    padding: 14mm 10mm 14mm;
    box-sizing: border-box;
    page-break-after: always;
  }

  .bahn-detail {
    height: 148mm;
    padding: 14mm 10mm 14mm;
    box-sizing: border-box;
    page-break-inside: avoid;
  }

  .bahn-detail:nth-of-type(2n) {
    page-break-after: always;
  }

  /* Packliste: wie eine Bahn eine halbe A4-Seite; mit Packliste teilt sie sich das Blatt mit der Titelseite
     (Titelseite nur halbe Höhe, kein Seitenumbruch dazwischen) */
  .anlage-intro.mit-packliste {
    height: 148mm;
    page-break-after: auto;
  }
  .bahn-detail.packliste-detail {
    page-break-after: always;
  }
  .packliste-detail .packliste-inhalt > div:first-child:not(.packliste-layout) { display: none; }
  /* kompakter, damit auch längere Packlisten in die halbe Seite passen */
  .bahn-detail.packliste-detail { gap: 2mm; }
  .packliste-detail .packliste-tabelle { font-size: 11px; }
  .packliste-detail .packliste-tabelle th, .packliste-detail .packliste-tabelle td { padding: 1px 0; }
  .packliste-detail .packliste-legende { margin-top: 1.5mm; }
  .bahn-layout.packliste-layout { grid-template-columns: minmax(0, 1fr) 50mm; gap: 5mm; }

  .bahn-layout {
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    gap: 8mm;
  }

  .bahn-detail h1 {
    font-size: 14pt;
    margin: 0;
  }

  .spielvarianten { gap: 4px; }
  .variante-karte { font-size: 9px; border-radius: 6px; }
  .karte-kopf { padding: 3px 7px; }
  .karte-titel { font-size: 10px; }
  .karte-wertung { font-size: 9px; }
  .karte-koerper { padding: 3px 7px 4px; gap: 3px; }
  .karte-koerper .lbl { font-size: 8px; }
  .karte-chips li { padding: 0 6px; }
}

/* Auswahllogik (Schritt 6) */
.variante-gruppe { display: block; }
.variante-gruppe[hidden], .variante-karte[hidden] { display: none; }
.karte-status {
  margin-left: auto;
  padding: 0 6px;
  border-radius: 8px;
  background: var(--pe-flaeche-sanft);
  color: var(--pe-text-gedaempft);
  font-size: 10px;
  white-space: nowrap;
}
.karte-kopf { gap: 6px; }

/* Schalter "Linie" in der Kartenüberschrift */
.linie-schalter {
  min-height: 24px;                 /* dichte Kartenzeile: Mindestgröße nach WCAG 2.5.8 statt 44 px */
  font: inherit;
  font-size: 11px;
  line-height: 1.5;
  padding: 0 10px;
  border: 2px solid var(--ball, var(--pe-ball-unbekannt));
  border-radius: var(--pe-radius-pille);
  background: var(--pe-weiss);
  color: var(--pe-petrol);
  cursor: pointer;
}
.linie-schalter[aria-pressed="true"] { background: var(--ball, var(--pe-ball-unbekannt)); color: var(--pe-weiss); text-shadow: 0 0 2px var(--pe-petrol), 0 0 1px var(--pe-petrol); }
.linie-schalter:focus-visible { outline: 3px solid var(--pe-petrol); outline-offset: 2px; }

/* Linien im Bahnplan */
.bahn-linie.ungenutzt, .bahn-linie.aus { display: none; }

@media print {
  .variante-gruppe.nicht-drucken { display: none; }
  /* Im Druck nur der Pfad der aktiven Variante, aus Lesbarkeitsgründen */
  .bahn-linie { display: none; }
  .bahn-linie.ist-aktiv:not(.aus):not(.ungenutzt) { display: inline; }
}
.karte-titel { flex: 1; }

/* Schritt 7: Fallback, "Möglich mit", "Alternative zu" */
.karte-zusatz { margin: 0; color: var(--pe-text-gedaempft); }
.bahn-fallback {
  margin: 0 0 8px;
  padding: 6px 10px;
  border: 1px dashed var(--pe-linie);
  border-radius: 10px;
  color: var(--pe-text-gedaempft);
  font-size: 12px;
}
.bahn-fallback p { margin: 0; font-weight: 600; }
.bahn-fallback-liste { margin: 4px 0 0; padding: 0; list-style: none; font-size: 11px; }
.bahn-fallback-liste .lbl { color: var(--pe-text-gedaempft); }
/* Am Bildschirm zeigen die gedämpften Karten die Details, die Liste gibt es nur im Druck */
.bahn-fallback-liste { display: none; }
@media print {
  .bahn-fallback-liste { display: block; }
  .bahn-fallback { font-size: 9px; padding: 3px 7px; }
  .bahn-fallback-liste { font-size: 8px; }
}

/* Schritt 8: eigene Wertung */
.wertung-panel { border: var(--pe-rahmen-fein) solid var(--pe-petrol); border-radius: var(--pe-radius-innen); padding: 8px 12px; background: var(--pe-weiss); max-width: 32rem; }
.wertung-panel h2 { margin: 0 0 4px; font-size: 16px; }
.wertung-panel p { margin: 4px 0; }
.wertung-zeile { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.wertung-zeile input { width: 5em; padding: 0 6px; }
.wertung-zeile input[aria-invalid="true"] { border-color: var(--pe-fehler); outline-color: var(--pe-fehler); }


.karte-wertung.eigene { font-weight: 700; }

/* Schritt 9: Profile */
.profil-verwaltung { margin-top: 1.5rem; max-width: 40rem; }
.profil-verwaltung h2 { font-size: 18px; }
.profil-verwaltung h3 { font-size: 15px; margin: 1rem 0 0.25rem; }
.profil-verwaltung textarea { width: 100%; font: 12px/1.4 ui-monospace, monospace; box-sizing: border-box; }
.profil-verwaltung button, .profil-verwaltung select { margin-right: 6px; }
.profil-stand { margin: 0.5rem 0 0; padding: 6px 12px; border: var(--pe-rahmen-fein) solid var(--pe-petrol); border-radius: var(--pe-radius-innen); background: var(--pe-weiss); }
.profil-quellen { display: flex; flex-wrap: wrap; gap: 4px 18px; margin-bottom: 0.5rem; }
.profil-quelle { cursor: pointer; }
.profil-meldung.gut { color: var(--pe-ok); }
.profil-meldung.schlecht { color: var(--pe-fehler); }

/* Schritt 10: Packliste */
.packliste-seite { max-width: 52rem; }
.packliste-inhalt h2 { font-size: 18px; margin: 1.25rem 0 0.4rem; }
.packliste-liste, .packliste-offen { list-style: none; margin: 0; padding: 0; }
.packliste-liste li { display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline; padding: 3px 0; border-bottom: 1px solid var(--pe-linie); }
.packliste-tabelle .ball-punkt { display: inline-block; width: 10px; height: 10px; border-radius: 50%; background: var(--ball, var(--pe-ball-unbekannt)); }
.packliste-liste .ausprobieren { color: var(--pe-text-gedaempft); }
.packliste-liste .lbl { color: var(--pe-text-gedaempft); font-size: 11px; }
.packliste-kauf { margin: 0; padding-left: 1.2rem; }
.packliste-layout .plan-karte-diagramm { max-width: none; margin: 0; }
.packliste-seite { min-width: 0; }
.packliste-layout > * { min-width: 0; }
.ball-chart a.ausprobieren circle:not(.ring) { fill-opacity: 0.4; stroke-dasharray: 3 2; }
.packliste-offen { display: flex; flex-wrap: wrap; gap: 4px 14px; }

/* Schritt 11: Druck-Feinschliff */
@media print {
  .packliste-seite { padding: 15mm; box-sizing: border-box; max-width: none; }
  .packliste-inhalt a { color: inherit; text-decoration: none; }
  .packliste-inhalt .ball-punkt,
  .packliste-inhalt .plan-karte,
  .variante-karte,
  .karte-kopf,
  .linie-schalter {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
  .packliste-inhalt h2 { break-inside: avoid; }
  /* Druck: Tabelle und Diagramm nebeneinander, das Diagramm schmal, damit alles auf die Einleitungsseite passt */
  .bahn-layout.packliste-layout { grid-template-columns: minmax(0, 1fr) 62mm; gap: 6mm; }
  .packliste-inhalt .packliste-tabelle { min-width: 0; }
  .packliste-inhalt .plan-karte-diagramm { max-width: none; }
  .bahn-detail a { color: inherit; text-decoration: none; }
}

/* Schritt 12: Filter und Sortierung auf der Bahn-Einzelseite */
.bahnfilter { display: flex; flex-wrap: wrap; gap: 8px 18px; margin: 0.5rem 0 1rem; }
.bahnfilter-feld { display: inline-flex; gap: 6px; align-items: center; }
.bahnfilter select { padding: 0 6px; }

/* ---------- Styleguide (/styleguide/) ---------- */
.sg-kopf { margin-bottom: 2rem; }
.sg-lead { font-family: var(--pe-schrift-titel); font-size: clamp(1.25rem, 3vw, 1.75rem); font-weight: 600; letter-spacing: -0.02em; line-height: 1.2; max-width: 40rem; }
.sg-inhalt { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 1rem; }
.sg-inhalt a { display: inline-flex; align-items: center; min-height: var(--pe-touch); padding: 0 1rem; border: var(--pe-rahmen-fein) solid var(--pe-petrol); border-radius: var(--pe-radius-pille); font-weight: 700; text-decoration: none; }
.sg-inhalt a:hover { background: var(--pe-planflaeche); }
.sg-abschnitt { margin-top: 3.5rem; scroll-margin-top: 1rem; }
.sg-abschnitt > h2 { padding-bottom: 0.4rem; border-bottom: var(--pe-rahmen) solid var(--pe-petrol); }
.sg-dreispaltig { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); gap: 1.5rem 2rem; align-items: start; }
.sg-formel { margin: 1rem 0; padding: 0.75rem 1rem; border: var(--pe-rahmen-fein) solid var(--pe-petrol); border-radius: var(--pe-radius-innen); background: var(--pe-weiss); overflow-x: auto; font: 0.85rem/1.5 var(--pe-schrift-code); }
.sg-zweispaltig { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr)); gap: 1.5rem 2.5rem; align-items: start; }
.sg-farben { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr)); gap: 1rem; }
.sg-farbfeld { margin: 0; border: var(--pe-rahmen) solid var(--pe-petrol); border-radius: var(--pe-radius-karte); overflow: hidden; background: var(--pe-weiss); }
.sg-muster { height: 7rem; padding: 0.75rem; display: flex; align-items: flex-start; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.sg-farbfeld figcaption { display: grid; gap: 0.2rem; padding: 0.75rem 1rem 1rem; border-top: var(--pe-rahmen) solid var(--pe-petrol); }
code { font-family: var(--pe-schrift-code); font-size: 0.85em; }
.sg-punkt { display: inline-block; width: 1rem; height: 1rem; border: 2px solid var(--pe-petrol); border-radius: 50%; vertical-align: middle; }
.sg-liste-kompakt { padding-left: 1.2rem; }
.sg-text { padding-left: 1.2rem; }
.sg-text li { margin-bottom: 0.4rem; }
.sg-kontrast { display: flex; flex-wrap: wrap; gap: 0.75rem; }
.sg-kontrast-ok { padding: 0.6rem 1rem; border-radius: var(--pe-radius-pille); font-weight: 700; }
.sg-schrift { padding: 1.25rem; border: var(--pe-rahmen) solid var(--pe-petrol); border-radius: var(--pe-radius-karte); background: var(--pe-weiss); }
.sg-schrift h3 { margin-top: 0; }
.sg-titel-gross { margin: 0; font-family: var(--pe-schrift-titel); font-weight: 800; letter-spacing: -0.04em; line-height: 0.95; font-size: clamp(2.5rem, 7vw, 4.5rem); }
.sg-titel-mittel { margin: 0.5rem 0 0; font-family: var(--pe-schrift-titel); font-weight: 700; letter-spacing: -0.03em; line-height: 1.05; font-size: 2rem; }
.sg-titel-klein { margin: 0.25rem 0 1rem; font-family: var(--pe-schrift-titel); font-weight: 500; letter-spacing: -0.03em; line-height: 1.1; font-size: 1.4rem; }
.sg-text-gross { font-size: 1.15rem; }
.sg-gewicht { display: inline-block; margin-right: 1rem; font-size: 1.4rem; }
.sg-logos { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr)); gap: 1rem; margin: 1rem 0; }
.sg-logo-karte { margin: 0; border: var(--pe-rahmen) solid var(--pe-petrol); border-radius: var(--pe-radius-karte); overflow: hidden; background: var(--pe-weiss); }
.sg-logo-karte figcaption { display: grid; gap: 0.2rem; padding: 0.75rem 1rem; border-top: var(--pe-rahmen) solid var(--pe-petrol); }
.sg-logo-flaeche { display: flex; align-items: flex-end; justify-content: center; gap: 1.5rem; padding: 1.5rem; min-height: 9rem; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.sg-flaeche-weiss { background: var(--pe-weiss); }
.sg-flaeche-petrol { background: var(--pe-petrol); }
.sg-lockup { align-items: center; border: var(--pe-rahmen) solid var(--pe-petrol); border-radius: var(--pe-radius-karte); }
.sg-zeile { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem; margin: 0.75rem 0; }
.sg-auf-petrol { padding: 1rem; border-radius: var(--pe-radius-karte); background: var(--pe-petrol); }
.sg-plan-schmal { max-width: 22rem; width: 100%; }
.hero-klein { grid-template-columns: minmax(0, 1fr); padding: 1.5rem; border-radius: var(--pe-radius-karte); }
.hero-klein h1 { font-size: 2.25rem; margin-bottom: 0.25rem; }
.sg-orange { margin-top: 1rem; padding: 1rem 1.25rem; border-radius: var(--pe-radius-karte); }
.sg-sonicht { padding: 1.25rem; border: var(--pe-rahmen) solid var(--pe-petrol); border-radius: var(--pe-radius-karte); background: var(--pe-weiss); }
.sg-sonicht p { margin: 0; }
.sg-nicht { border-style: dashed; }
.ball-chip-falsch { box-shadow: 0 0 0 3px var(--pe-weiss), 0 0 0 7px var(--pe-orange); }   /* nur als Gegenbeispiel */
.sg-sonicht .sg-zeile { padding: 0.5rem; }
.druckvorschau { border: var(--pe-rahmen-fein) dashed var(--pe-petrol); border-radius: var(--pe-radius-innen); overflow: hidden; background: var(--pe-weiss); }
.druckvorschau .druckkopf { display: flex; position: static; height: auto; margin: 0.5rem 1rem 0; padding: 0 0 0.4rem; align-items: center; gap: 0.75rem; border-bottom: 2px solid var(--pe-petrol); color: var(--pe-petrol); }
.druckvorschau .druckkopf .logo-mark { --logo-hoehe: 28px; }
.druckvorschau .druckkopf-wort { font-family: var(--pe-schrift-titel); font-weight: 800; letter-spacing: -0.03em; }
.druckvorschau .druckkopf-anlage { margin-left: auto; display: inline-flex; align-items: center; gap: 0.5rem; font-weight: 700; }
.druckvorschau .druckfuss { display: block; margin: 0 1rem 0.75rem; padding-top: 0.4rem; border-top: 2px solid var(--pe-petrol); font-size: 0.8rem; }
.druckvorschau-inhalt { padding: 1rem; }
.sg-tabelle { width: 100%; border-collapse: collapse; }
.sg-tabelle code { overflow-wrap: anywhere; }
.sg-tabelle th, .sg-tabelle td { text-align: left; vertical-align: top; padding: 0.6rem 0.75rem; border-bottom: 1px solid var(--pe-linie); }
.sg-tabelle thead th { border-bottom: var(--pe-rahmen) solid var(--pe-petrol); }
.code-block { margin-top: 1.25rem; border: var(--pe-rahmen-fein) solid var(--pe-petrol); border-radius: var(--pe-radius-innen); background: var(--pe-weiss); }
.code-block > summary { cursor: pointer; padding: 0.6rem 1rem; min-height: var(--pe-touch); display: flex; align-items: center; font-weight: 700; }
.code-pfad { margin: 0.5rem 1rem 0.25rem; font-size: 0.9rem; }
.code-block .highlight { margin: 0 1rem 1rem; }
.code-block pre { margin: 0; padding: 1rem; max-height: 28rem; overflow: auto; border-radius: 12px; background: var(--pe-petrol); color: var(--pe-text-auf-petrol); font: 0.8rem/1.5 var(--pe-schrift-code); }
.code-block pre code { font: inherit; }
.code-block .chroma .c, .code-block .chroma .c1, .code-block .chroma .cm, .code-block .chroma .cs, .code-block .chroma .cp, .code-block .chroma .ch { color: var(--pe-planflaeche); opacity: 0.7; font-style: italic; }
.code-block .chroma .k, .code-block .chroma .kd, .code-block .chroma .kn, .code-block .chroma .kr, .code-block .chroma .nt, .code-block .chroma .nl, .code-block .chroma .nb { color: var(--pe-orange); }
.code-block .chroma .s, .code-block .chroma .s1, .code-block .chroma .s2, .code-block .chroma .sa, .code-block .chroma .sb, .code-block .chroma .sd, .code-block .chroma .se, .code-block .chroma .si { color: var(--pe-creme); }
.code-block .chroma .na, .code-block .chroma .nf, .code-block .chroma .nv, .code-block .chroma .vm, .code-block .chroma .py { color: var(--pe-planflaeche); }
.code-block .chroma .m, .code-block .chroma .mi, .code-block .chroma .mf, .code-block .chroma .mh { color: var(--pe-orange); }
@media print { .code-block { display: none; } }

/* Schmale Bildschirme: Bahnseite einspaltig, Tabelle über dem Plan */
@media screen and (max-width: 640px) {
  .bahn-layout,
  .bahn-folge .bahn-layout { grid-template-columns: minmax(0, 1fr); }
  .bahn-folge .bahn-plan-wrap { --plan-hoehe: 70vh; }
  .hero { grid-template-columns: minmax(0, 1fr); }
  .home-links { grid-template-columns: minmax(0, 1fr); }
  .spielvarianten-teaser { grid-template-columns: minmax(0, 1fr); }
  .site-header { height: auto; min-height: var(--kopf-hoehe); flex-wrap: wrap; padding-block: 0.5rem; }
}

/* Packliste: Tabelle Ball x Bahn mit Symbolen (● Hauptball, ◆ Alternative, ○ Ausprobieren) */
.packliste-tabelle-wrap { overflow-x: auto; position: relative; }
.packliste-tabelle { border-collapse: collapse; width: 100%; table-layout: fixed; min-width: 30rem; font-size: 14px; }
.packliste-tabelle th, .packliste-tabelle td { padding: 3px 0; border-bottom: 1px solid var(--pe-linie, #d9d6cc); text-align: center; }
.packliste-tabelle th.pt-ball { text-align: left; padding-right: 8px; font-weight: 500; line-height: 1.25; }
.packliste-tabelle thead th { font-size: 12px; color: var(--pe-text-gedaempft); font-weight: 600; }
.packliste-tabelle .pt-bahn { width: 0.7rem; min-width: 0.7rem; font-size: 9px; letter-spacing: -0.05em; }
.packliste-tabelle .pt-ball .ball-punkt { margin-right: 6px; vertical-align: baseline; }
.packliste-tabelle tr.ausprobieren th.pt-ball { color: var(--pe-text-gedaempft); }
.packliste-tabelle .pt-haupt, .packliste-tabelle .pt-alternative { color: var(--pe-petrol); }
.packliste-tabelle .pt-ausprobieren { color: var(--pe-text-gedaempft); }
.packliste-legende { margin: 0.5rem 0 0; font-size: 13px; display: flex; flex-wrap: wrap; gap: 0.25rem 1rem; }
.packliste-legende .pt-zelle { border: 0; padding: 0; }
/* Symbole gezeichnet statt als Schriftzeichen: ● Hauptball, ◐ Alternative (halb gefüllt), ○ Ausprobieren */
.pt-symbol { display: inline-block; width: 0.6rem; height: 0.6rem; border-radius: 50%; border: 1.5px solid currentColor; box-sizing: border-box; vertical-align: middle; }
.pt-symbol { -webkit-print-color-adjust: exact; print-color-adjust: exact; }   /* Füllung ist ein Hintergrund: sonst druckt der Browser nur den leeren Ring */
.pt-haupt .pt-symbol { background: currentColor; }
.pt-alternative .pt-symbol { background: linear-gradient(90deg, currentColor 50%, transparent 50%); }
.pt-ausprobieren .pt-symbol { background: transparent; }
.packliste-tabelle th:nth-child(3n), .packliste-tabelle td:nth-child(3n),
.packliste-tabelle th:nth-child(2), .packliste-tabelle td:nth-child(2) { border-left: 1px solid var(--pe-linie); }
/* Ballwerte (Härte, Sprung, Gewicht) vor den Bahnspalten; Endlinie hinter der letzten Bahn */
.packliste-tabelle .pt-eig { width: 3.5rem; text-align: right; padding-right: 5px; font-size: 10.5px; letter-spacing: -0.06em; white-space: nowrap; color: var(--pe-text-gedaempft); font-variant-numeric: tabular-nums; }
.packliste-tabelle thead .pt-eig { font-size: 10px; font-weight: 400; }
.packliste-tabelle th:last-child, .packliste-tabelle td:last-child { border-right: 1px solid var(--pe-linie); }

Farbkonzept „Marke als Bühne“

Pläne werden in den Farben der jeweiligen Anlage gerendert, Spielvarianten darauf in der Ballfarbe. Das hat Wiedererkennungswert, vor allem im Druck. Die Lösung in vier Regeln:

  1. Sockel: Pläne, Thumbnails, Bälle und Diagramme behalten ihre Echtfarben und sitzen immer auf der neutralen Planfläche in einer Karte mit Petrol-Rahmen. Alles drumherum bleibt strikt Petrol / Orange / Creme.
  2. Farbchip: Die Anlagenfarbe erscheint zusätzlich als kleiner runder Farbchip (22 px, 3 px Petrol-Rand) am Anlagennamen, im Seitenkopf und im Druckkopf.
  3. Ring statt Orange: Auswahl und Hervorhebung zeigt ein Petrol-Ring (3–4 px, dann ein weißer Spalt, dann der Ball).
  4. Ball nie nur über Farbe: Jeder Ball bekommt zusätzlich einen Buchstaben (Anfangsbuchstabe des Ballnamens, überschreibbar mit dem Feld kuerzel).

Zwei Beispielanlagen

Beispielanlage Grau/BlauFarbchip, Plan, Linien
Beispielanlage Rot/WeißFarbchip, Plan, Linien

Die Farben kommen aus dem Front Matter der Anlage (farben: chip, bahn, linie) und setzen die CSS-Variablen --plan-chip, --plan-bahn, --plan-linie. Die Bahnzeichnungen bleiben unverändert: die SVG-Dateien lesen die Variablen mit dem Normwert als Rückfall. Jeder Plan braucht eine eigene Rand- oder Linienfarbe, damit auch ein graues Bahnfeld erkennbar bleibt.

Flächenfarbe aus der Bandenfarbe

Die Fläche der Bahn wird nicht einzeln festgelegt, sondern aus der Bandenfarbe abgeleitet: ein Anteil der Bandenfarbe auf Weiß. Das spart Tinte (im Druck bleibt es eine helle Tönung), und Bande und Fläche passen immer zusammen. Eine Anlage braucht damit nur noch bande, optional ablege (Ablegelinie) und flaeche_anteil.

Rot30 % → #ECC4C4
Betongrau22 % → #E2E4E6
Blau30 % → #C1D1E5
--plan-flaeche: color-mix(in srgb, var(--plan-bande) 30%, white);

Richtwert: 30 % Bandenfarbe, bei sehr hellen oder grauen Anlagen weniger (z. B. 22 %). Bei grauen Anlagen hebt sich die Fläche kaum von der Planfläche (#DCE8E5) ab, die Bahn wird dort von der Bande getragen. Eine weiße Ablegelinie braucht eine genügend getönte Fläche, im Druck zeigt sie das Papier. Das Feld flaeche_anteil setzt die Fläche nur, solange flaeche und bahn nicht gesetzt sind. Die Beispiele zeigen die Bahn ohne Spielvarianten-Linien.

So / Nicht so

B

So: Auswahl = Petrol-Ring. Der Ball bleibt in Echtfarbe, mit Buchstabe.

B

Nicht so: Orange Markenfarbe als Auswahl verschwimmt mit orangen Bällen.

Druckvorschau

Plan in Echtfarben. Darüber ein Druckkopf aus Marke, Anlagenchip und einer Petrol-Linie (keine gefüllte Fläche, das spart Tinte), darunter eine Fußzeile mit Petrol-Linie. Die Planfläche ist im Druck weiß, die Linien bleiben. Jeder Block ist eine halbe A4-Seite (148,5 mm) mit Schnittmarken auf halber Höhe; nach dem Zuschneiden entstehen A5-Blätter quer mit 10 mm Rand seitlich und 5 mm unbedruckbarem Rand oben und unten. Für farbige Flächen gilt print-color-adjust: exact.

Code

layouts/partials/anlage-chip.html

{{- /* Farbe des Farbchips einer Anlage. Eingabe: Seite der Anlage (oder der Bahn; dann gilt die Anlage darüber,
       wenn die Bahn nichts setzt). Reihenfolge: farben.chip, farben.bahn, farben.flaeche. Rückgabe: Hex-Wert
       oder "" (dann zeigt farbchip.html einen leeren Creme-Chip). */ -}}
{{- $f := dict -}}
{{- with .Params.farben }}{{ $f = . }}{{ end -}}
{{- $chip := "" -}}
{{- range slice "chip" "bahn" "flaeche" -}}
  {{- with index $f . -}}{{- if not $chip }}{{ $chip = . }}{{ end -}}{{- end -}}
{{- end -}}
{{- return $chip -}}

layouts/partials/bahn-plan-wrap.html

{{- /* Bahnplan mit Linien-Overlay (gemeinsam für Bahn- und Variantenseite).
       Eingabe (dict):
         bahn        Seite der Bahn (Felder svg, svg_alt, gespiegelt, farben, hindernisse)
         varianten   Liste der Spielvarianten-Seiten, deren Linien gezeichnet werden
         alle_aktiv  optional (bool): Linien gelten im Druck alle als aktiv (Variantenseite)
         alternativen_aus  optional (bool): Linien der Alternativbälle zunächst ausblenden;
                     bahnansicht.js blendet je Variante die Linie des gewählten Balls ein (Bahnseite)
         farben      optional (dict): Farben, die Anlage und Bahn überschreiben (Styleguide: Beispielanlagen)
       Diese Datei liefert nur den Plan samt Overlay; die Plan-Karte (Sockel) setzt bahn-plan.html darum.
       Je Variante und Ball ein Pfad: Alternativen erben die Linie der Variante, sofern sie keine
       eigene haben. Die Koordinaten liegen in der viewBox des Bahnplans.

       Der Bahnplan wird inline eingebettet (nicht als <img>), damit Anlagen ihn anpassen können:
         farben        (Front Matter der Anlage und/oder der Bahn; Bahn überschreibt Anlage)
                       flaeche_anteil (Prozent, optional): leitet die Fläche als Mischung aus Bandenfarbe und Weiß ab
                       (color-mix), solange flaeche und bahn nicht gesetzt sind.
                       Rollen flaeche, bande, grenze, ablege, hilfslinie, dunkel, grau, stein mit Hexwert #RRGGBB. Sie setzen die
                       CSS-Variablen --plan-<rolle>, die in den SVG-Dateien mit dem Normwert als Rückfall stehen.
                       Kurzformen: bahn (= flaeche) und linie (= bande, grenze, ablege, hilfslinie, soweit nicht einzeln
                       gesetzt) setzen --plan-bahn und --plan-linie; die Zuordnung zu den Rollen steht in style.css
                       (.bahn-plan-wrap). chip (--plan-chip) ist die Farbe des Farbchips und färbt den Plan nicht.
         gespiegelt    (Front Matter der Bahn) true: Plan und Linien-Overlay werden spiegelbildlich dargestellt
                       (Norm erlaubt das nur für ausgewählte Bahnen, siehe scripts/validate_content.py).
                       Pfade in "linie" bleiben in den Koordinaten des ungespiegelten Plans.
         hindernisse   (Front Matter der Bahn) Pfad zu einer SVG-Datei unter static/, deren Gruppe
                       <g id="hindernisse">...</g><!--/hindernisse--> die Hindernisse des Standardplans ersetzt.
                       Die Datei sollte dieselbe viewBox wie der Standardplan haben.
       Die Bild-IDs aus der SVG (z.B. Verlauf "trichter") müssen je Seite eindeutig sein; ein Plan pro Seite. */ -}}
{{- $bahn := .bahn -}}
{{- $svgText := "" -}}
{{- with $bahn.Params.svg -}}
  {{- $svgPfad := printf "static%s" . -}}
  {{- if fileExists $svgPfad -}}
    {{- $svgText = readFile $svgPfad -}}
  {{- end -}}
{{- end -}}
{{- /* Hindernisse der Anlage einsetzen */ -}}
{{- with $bahn.Params.hindernisse -}}
  {{- $hPfad := printf "static%s" . -}}
  {{- $muster := `(?s)<g id="hindernisse">.*?</g><!--/hindernisse-->` -}}
  {{- if not (fileExists $hPfad) -}}
    {{- warnf "bahn-plan: hindernisse-Datei %q der Bahn %q existiert nicht" $hPfad $bahn.Path -}}
  {{- else -}}
    {{- $neu := findRE $muster (readFile $hPfad) 1 -}}
    {{- $alt := findRE $muster $svgText 1 -}}
    {{- if and $neu $alt -}}
      {{- $svgText = replace $svgText (index $alt 0) (index $neu 0) 1 -}}
    {{- else -}}
      {{- warnf "bahn-plan: Gruppe <g id=\"hindernisse\"> fehlt in %q oder im Plan der Bahn %q" $hPfad $bahn.Path -}}
    {{- end -}}
  {{- end -}}
{{- end -}}
{{- /* viewBox lesen, SVG für die Einbettung bereinigen und auszeichnen */ -}}
{{- $viewBox := "" -}}
{{- with $svgText -}}
  {{- with findRESubmatch `viewBox="([^"]+)"` . 1 -}}
    {{- $viewBox = index (index . 0) 1 -}}
  {{- end -}}
  {{- $svgText = replaceRE `^\s*<\?xml[^>]*\?>\s*` "" $svgText -}}
  {{- $svgText = replaceRE `(?s)\s*<metadata>.*?</metadata>` "" $svgText -}}
  {{- $svgText = replaceRE ` xmlns:c2pa="[^"]*"` "" $svgText -}}
  {{- $svgText = replaceRE `(?s)\s*<!--.*?-->` "" $svgText -}}
  {{- $svgText = replace $svgText "<svg " (printf `<svg class="bahn-plan" role="img" aria-label="%s" preserveAspectRatio="xMidYMin meet" ` ($bahn.Params.svg_alt | default $bahn.Title | htmlEscape)) 1 -}}
{{- end -}}
{{- /* Farben: Anlage, dann Bahn überschreibt */ -}}
{{- $farben := dict -}}
{{- with $bahn.Parent }}{{ range $k, $v := .Params.farben }}{{ $farben = merge $farben (dict $k $v) }}{{ end }}{{ end -}}
{{- range $k, $v := $bahn.Params.farben }}{{ $farben = merge $farben (dict $k $v) }}{{ end -}}
{{- range $k, $v := .farben }}{{ $farben = merge $farben (dict $k $v) }}{{ end -}}
{{- $stil := "" -}}
{{- range $rolle := slice "flaeche" "bande" "grenze" "ablege" "hilfslinie" "dunkel" "grau" "stein" "bahn" "linie" "chip" -}}
  {{- with index $farben $rolle -}}
    {{- if findRE `^#[0-9A-Fa-f]{3,8}$` (string .) -}}
      {{- $stil = printf "%s--plan-%s:%s;" $stil $rolle . -}}
    {{- end -}}
  {{- end -}}
{{- end -}}
{{- /* flaeche_anteil (Prozent, 1-100): Fläche aus der Bandenfarbe ableiten, sofern flaeche/bahn nicht gesetzt sind */ -}}
{{- with index $farben "flaeche_anteil" -}}
  {{- if and (not (index $farben "flaeche")) (not (index $farben "bahn")) (findRE `^\d{1,3}$` (string .)) -}}
    {{- $stil = printf "%s--plan-flaeche:color-mix(in srgb, var(--plan-bande, #6b6a63) %s%%, white);" $stil (string .) -}}
  {{- end -}}
{{- end -}}
{{- $alleAktiv := .alle_aktiv -}}
{{- $altAus := .alternativen_aus -}}
<div class="bahn-plan-wrap{{ if $bahn.Params.gespiegelt }} gespiegelt{{ end }}"{{ with $stil }} style="{{ . | safeCSS }}"{{ end }}>
  {{ with $svgText }}{{ . | safeHTML }}{{ end }}
  {{ if $viewBox }}
    <svg class="bahn-linien" viewBox="{{ $viewBox }}" preserveAspectRatio="xMidYMin meet" aria-hidden="true">
      {{ range .varianten }}
        {{ $v := . }}
        {{ $id := partial "variante-id.html" $v }}
        {{ $eintraege := slice (dict "ball" $v.Params.ball "haupt" true) }}
        {{ range $v.Params.alternativen }}
          {{ $eintraege = $eintraege | append (dict "ball" .ball "o" . "haupt" false) }}
        {{ end }}
        {{ range $eintraege }}
          {{ $linie := partial "variante-wert.html" (dict "v" $v "o" .o "feld" "linie") }}
          {{ if $linie.gesetzt }}
            {{ $farbe := "#2c2c2a" }}
            {{ with site.GetPage (printf "/baelle/%s" .ball) }}{{ with .Params.farbe }}{{ $farbe = . }}{{ end }}{{ end }}
            <path class="bahn-linie{{ if and $altAus (not .haupt) }} ungenutzt{{ end }}{{ if $alleAktiv }} ist-aktiv{{ end }}"
                  d="{{ $linie.wert }}" stroke="{{ $farbe }}" data-variante="{{ $id }}" data-ball="{{ .ball }}"/>
          {{ end }}
        {{ end }}
      {{ end }}
    </svg>
  {{ end }}
</div>

layouts/partials/druckrahmen.html

{{- /* Druckkopf (Marke und Anlagenchip, nur Petrol-Linie, keine Fläche: spart Tinte) und Fußzeile (Petrol-Linie) einer gedruckten Seite oder Bahn.
       Am Bildschirm unsichtbar (display: none); im Druck liegen beide absolut im Seitenrand der Seite
       (.bahn-detail, .anlage-intro), im Schnittrand der halben A4-Seite (A5 quer nach dem Zuschneiden).
       Eingabe (dict): anlage (Seite der Anlage), titel (z.B. Name der Bahn; optional) */ -}}
{{- $anlage := .anlage -}}
<div class="druckkopf" aria-hidden="true">
  {{ partial "logo-mark.html" (dict "variant" "favicon" "dekorativ" true) }}
  <span class="druckkopf-wort">{{ site.Title }}</span>
  <span class="druckkopf-anlage">{{ partial "farbchip.html" (partial "anlage-chip.html" $anlage) }} {{ $anlage.Title }}</span>
</div>
<div class="druckfuss" aria-hidden="true">{{ site.Title }} · {{ $anlage.Title }}{{ with .titel }} · {{ . }}{{ end }}</div>

layouts/partials/streudiagramm.html

{{- /* Streudiagramm der Bälle (Härte x Sprunghöhe). Eingabe: Liste der Ball-Seiten (z.B. .Pages der Bälle-Seite).
       Die Bälle behalten ihre Echtfarben, dazu Kürzel im Kreis (nie nur Farbe); der Hintergrund kommt von der
       Plan-Karte (Planfläche). ballauswahl.js blendet über [data-ball-slug] nicht ausgewählte Bälle aus. */ -}}
{{ $balls := . }}
{{ $plotLeft := 70 }}
{{ $plotRight := 320 }}
{{ $plotTop := 60 }}
{{ $plotBottom := 380 }}
{{- /* Sprunghöhe: Achse bis 85 cm (höher springt kein Ball), 320 Einheiten Achsenlänge. Bälle ohne Angabe liegen
       außerhalb der Achsen: ohne Härte links der y-Achse (bei -10), ohne Sprunghöhe unterhalb der x-Achse (bei -9 cm). */ -}}
{{ $sprungMax := 85.0 }}
{{ $faktorY := div (sub $plotBottom $plotTop) $sprungMax }}
{{ $haerteFehlt := -10 }}
{{ $sprungFehlt := -9 }}

<svg class="ball-chart" viewBox="0 0 380 485" role="img" xmlns="http://www.w3.org/2000/svg">
  <title>Streudiagramm der ausgewählten Bälle</title>
  <desc>Sprunghöhe auf der y-Achse, Härte auf der x-Achse. Kreisgröße zeigt die
  Ballgröße, Randdicke das Gewicht, Füllmuster die Oberfläche, Farbe die
  individuelle Optik. Es erscheinen nur die ausgewählten Bälle (ohne Auswahl alle).
  Der Name steht im Tooltip, ein Klick auf einen Ball führt zur Detailseite.</desc>

  <defs>
    <marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
      <path d="M2 1L8 5L2 9" fill="none" stroke="var(--pe-text-gedaempft)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
    </marker>
  </defs>

  <!-- Achsen -->
  <line x1="{{ $plotLeft }}" y1="{{ $plotBottom }}" x2="{{ $plotLeft }}" y2="55" stroke="var(--pe-text-gedaempft)" stroke-width="0.5" marker-end="url(#arrow)"/>
  <line x1="{{ $plotLeft }}" y1="{{ $plotBottom }}" x2="325" y2="{{ $plotBottom }}" stroke="var(--pe-text-gedaempft)" stroke-width="0.5" marker-end="url(#arrow)"/>
  <text class="axis-label" x="{{ $plotLeft }}" y="42" text-anchor="middle">Sprunghöhe</text>
  {{- /* Hilfslinien der Sprunghöhe; Beschriftung rechts, damit die Bälle ohne Härte links der Achse frei bleiben */ -}}
  {{- range slice 20 40 60 80 }}
  <line x1="{{ $plotLeft }}" y1="{{ sub $plotBottom (mul . $faktorY) }}" x2="334" y2="{{ sub $plotBottom (mul . $faktorY) }}" stroke="var(--pe-text-gedaempft)" stroke-width="0.5" stroke-dasharray="2 4" opacity="0.6"/>
  <text x="338" y="{{ add (sub $plotBottom (mul . $faktorY)) 4 }}" style="font-size: 10px">{{ . }} cm</text>
  {{- end }}
  <text x="{{ $plotLeft }}" y="445" text-anchor="middle">weich</text>
  <text x="320" y="445" text-anchor="middle">hart</text>
  <text class="axis-label" x="290" y="468" text-anchor="middle">Härte</text>

  <!-- Bälle -->
  {{ range $index, $ball := $balls }}
    {{ $haerte := $haerteFehlt }}
    {{ with $ball.Params.haerte }}{{ $haerte = . }}{{ end }}
    {{ $sprung := $sprungFehlt }}
    {{ with $ball.Params.sprunghoehe }}{{ $sprung = . }}{{ end }}
    {{ $groesse := $ball.Params.groesse | default "K" }}
    {{ $gewicht := $ball.Params.gewicht | default "10" }}
    {{ $oberflaeche := $ball.Params.oberflaeche | default "L" }}
    {{ $farbe := $ball.Params.farbe | default "#378ADD" }}

    {{ $x := add $plotLeft (mul $haerte 2.5) }}
    {{ $y := sub $plotBottom (mul $sprung $faktorY) }}

    {{ $r := 12 }}
    {{ if or (eq $groesse "K") (eq $groesse "klein") }}{{ $r = 8 }}{{ else if or (eq $groesse "G") (eq $groesse "gross") }}{{ $r = 16 }}{{ end }}

    {{ $stroke := 2.5 }}
    {{ if eq $gewicht "leicht" }}{{ $stroke = 1.5 }}{{ else if eq $gewicht "schwer" }}{{ $stroke = 4 }}{{ end }}

    {{ $kuerzel := partialCached "ball-kuerzel.html" $ball $ball.File.ContentBaseName }}
    {{ $kontrast := partial "farb-kontrast.html" $farbe }}
    {{ $schrift := mul $r 1.1 }}{{ if eq (len $kuerzel) 2 }}{{ $schrift = mul $r 0.95 }}{{ else if gt (len $kuerzel) 2 }}{{ $schrift = mul $r 0.75 }}{{ end }}
    <a href="{{ $ball.RelPermalink }}" data-ball-slug="{{ $ball.File.ContentBaseName }}">
      {{- /* Auswahl-Ring (nur bei Hover/Fokus sichtbar): Petrol-Ring, weißer Spalt, dann der Ball */ -}}
      <circle class="ring ring-aussen" cx="{{ $x }}" cy="{{ $y }}" r="{{ add $r 7.25 }}"/>
      <circle class="ring ring-spalt" cx="{{ $x }}" cy="{{ $y }}" r="{{ add $r 4 }}"/>
      <circle cx="{{ $x }}" cy="{{ $y }}" r="{{ $r }}" fill="{{ $farbe }}" stroke="{{ $farbe }}" stroke-width="{{ $stroke }}" stroke-opacity="0.6"/>
      {{ if eq $oberflaeche "X" }}
        <circle cx="{{ $x }}" cy="{{ $y }}" r="{{ $r }}" fill="url(#dots)"/>
      {{ else if eq $oberflaeche "BL" }}
        <circle cx="{{ $x }}" cy="{{ $y }}" r="{{ $r }}" fill="url(#hatch)"/>
      {{ end }}
      <text class="ball-buchstabe" x="{{ $x }}" y="{{ $y }}" style="fill: var(--pe-{{ $kontrast }}); font-size: {{ $schrift }}px">{{ $kuerzel }}</text>
      <title>{{ $ball.Title }}</title>
    </a>
  {{ end }}

</svg>

Umsetzung

Wo liegt was

WasWo
Tokens (Farben, Radien, Raster)static/css/tokens.css
Komponenten-CSS, Druckstatic/css/style.css
Schriften (self-hosted)static/fonts/, geholt mit scripts/fonts-holen.sh; @font-face in tokens.css
Logolayouts/partials/logo-mark.html (hell, invertiert, favicon), logo.html (Lockup)
Favicon und Share-Bildstatic/favicon.svg, favicon-32.png, apple-touch-icon.png, static/images/share.png; erzeugt mit scripts/logo-assets.py
Komponentenlayouts/partials/: plan-karte, farbchip, ball-chip, button, kachel, bahn-plan
AnlagenfarbenFront Matter der Anlage: farben: chip, bahn, linie (und die Rollen flaeche, bande …)
BallkürzelOptionales Feld kuerzel der Ballseite, sonst Anfangsbuchstabe des Balltitels (ball-kuerzel.html)
Druckkopf und Fußzeilelayouts/partials/druckrahmen.html, CSS im @media print von style.css
Diese Seitecontent/styleguide/_index.md, layouts/styleguide/list.html