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
#003B40 --pe-petrol
#FF7A00 --pe-orange
#F7F7F2 --pe-creme
#DCE8E5 --pe-planflaeche
Weitere Töne
--pe-text-gedaempftSekundärtext auf hellem Grund--pe-text-auf-petrolFließtext auf Petrol--pe-weissKarten auf Creme--pe-linie,--pe-flaeche-sanftabgeleitet (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).
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-heightca. 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-height1,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"
Logo
Das Logo ist ein P als Minigolfbahn in der Draufsicht: gerundete Betonbahn mit Bande, bündiger oranger Abschlagbereich unten, durchgängige senkrechte Wand, Bogen, der kurz vor der Senkrechten in einem Endkreis endet, Loch nur als Ring, gepunktete Linie vom Abschlag zum Loch, petrolfarbener Ball im Bogen. Keine Fahne.
Lockup
- Marke ca. 44 px hoch, Abstand 12–16 px, Text „Putt happens“ in Bricolage Grotesque 800,
letter-spacing−0,03em, in Petrol (auf Petrol in Creme). - Mindestgröße der Marke: 32 px Höhe, darunter die Favicon-Variante.
- Keine Fahne am Loch, auch nicht in Varianten.
- Dateien:
static/favicon.svg,favicon-32.png,apple-touch-icon.png,images/share.png(erzeugt mitscripts/logo-assets.py).
Code
layouts/partials/logo-mark.html
{{- /* Marke „P als Minigolfbahn“ in der Draufsicht (keine Fahne).
Eingabe: Variante als String oder dict mit
variant "hell" (Standard, auf Creme/Weiß) | "invertiert" (auf Petrol) | "favicon" (klein, ab 16 px;
ohne gepunktete Linie und Ball, kräftigerer Lochring)
hoehe optional: Höhe in px (Standard: per CSS --logo-hoehe, sonst 44 px)
dekorativ optional (bool): aria-hidden statt role="img" (wenn der Name direkt daneben steht, z.B. im Lockup)
invertiert optional (bool): nur für "favicon" – Farben der Variante "invertiert" (z.B. im Druckkopf auf Petrol)
Farben sind Teil der Marke und stehen deshalb hier und nicht in den Tokens. Zeichnung: viewBox 12 4 76 92. */ -}}
{{- $o := dict "variant" "hell" -}}
{{- if reflect.IsMap . }}{{ $o = merge $o . }}{{ else if . }}{{ $o = merge $o (dict "variant" .) }}{{ end -}}
{{- $v := $o.variant -}}
{{- $inv := false -}}
{{- if or (eq $v "invertiert") (and (eq $v "favicon") $o.invertiert) }}{{ $inv = true }}{{ end -}}
{{- $petrol := "#003B40" -}}
{{- $orange := "#FF7A00" -}}
{{- $creme := "#F7F7F2" -}}
{{- $aussen := cond $inv $creme $petrol -}}
{{- $innen := cond $inv $petrol $creme -}}
{{- $bahn := "M28 92 V20 H52 A19 19 0 0 1 52 58" -}}
<svg class="logo-mark" xmlns="http://www.w3.org/2000/svg" viewBox="12 4 76 92"{{ with $o.hoehe }} style="--logo-hoehe: {{ . }}px"{{ end }}{{ if $o.dekorativ }} aria-hidden="true" focusable="false"{{ else }} role="img" aria-label="Putt happens"{{ end }}>
<path d="{{ $bahn }}" fill="none" stroke="{{ $aussen }}" stroke-width="22" stroke-linejoin="round"/>
<circle cx="52" cy="58" r="11" fill="{{ $aussen }}"/>
<path d="{{ $bahn }}" fill="none" stroke="{{ $innen }}" stroke-width="14" stroke-linejoin="round"/>
<circle cx="52" cy="58" r="7" fill="{{ $innen }}"/>
<rect x="21" y="78" width="14" height="14" fill="{{ $orange }}"/>
{{- if ne $v "favicon" }}
<path d="M28 75 V26 Q28 20 34 20 H52 A19 19 0 0 1 60 56.2" fill="none" stroke="{{ $orange }}" stroke-width="2.6" stroke-dasharray="0.1 6" stroke-linecap="round"/>
<circle cx="52" cy="58" r="4.6" fill="none" stroke="{{ $aussen }}" stroke-width="1.8"/>
<circle cx="69.9" cy="32.5" r="4.8" fill="{{ $aussen }}"/>
{{- else }}
<circle cx="52" cy="58" r="4.4" fill="none" stroke="{{ $aussen }}" stroke-width="3"/>
{{- end }}
</svg>
layouts/partials/logo.html
{{- /* Lockup: Marke plus Wortmarke „Putt happens“ (Bricolage Grotesque 800).
Eingabe (optional): dict mit
invertiert (bool) Creme auf Petrol statt Petrol auf hellem Grund
hoehe Höhe der Marke in px (Standard 44, Mindestgröße 32; darunter die Favicon-Variante)
link (bool) Lockup als Link zur Startseite
Die Wortmarke bleibt als Text stehen (Screenreader); die Marke daneben ist dekorativ. */ -}}
{{- $o := dict "invertiert" false "hoehe" 44 "link" false -}}
{{- if reflect.IsMap . }}{{ $o = merge $o . }}{{ end -}}
{{- $variante := cond $o.invertiert "invertiert" "hell" -}}
{{- $klein := lt (int $o.hoehe) 32 -}}
{{- if $klein }}{{ $variante = "favicon" }}{{ end -}}
{{- $inhalt := printf `%s<span class="logo-wort">%s</span>`
(partial "logo-mark.html" (dict "variant" $variante "dekorativ" true "invertiert" $o.invertiert))
(site.Title | htmlEscape) -}}
{{- $klasse := printf "logo%s" (cond $o.invertiert " auf-petrol" "") -}}
{{- if $o.link -}}
<a class="{{ $klasse }}" href="{{ "/" | relURL }}" style="--logo-hoehe: {{ $o.hoehe }}px" aria-label="{{ site.Title }} – Startseite">{{ $inhalt | safeHTML }}</a>
{{- else -}}
<span class="{{ $klasse }}" style="--logo-hoehe: {{ $o.hoehe }}px">{{ $inhalt | safeHTML }}</span>
{{- end -}}
static/favicon.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="12 4 76 92">
<path d="M28 92 V20 H52 A19 19 0 0 1 52 58" fill="none" stroke="#003B40" stroke-width="22" stroke-linejoin="round"/>
<circle cx="52" cy="58" r="11" fill="#003B40"/>
<path d="M28 92 V20 H52 A19 19 0 0 1 52 58" fill="none" stroke="#F7F7F2" stroke-width="14" stroke-linejoin="round"/>
<circle cx="52" cy="58" r="7" fill="#F7F7F2"/>
<rect x="21" y="78" width="14" height="14" fill="#FF7A00"/>
<circle cx="52" cy="58" r="4.4" fill="none" stroke="#003B40" stroke-width="3"/>
</svg>
Komponenten
Buttons und Tags
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
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
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
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:
- 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.
- Farbchip: Die Anlagenfarbe erscheint zusätzlich als kleiner runder Farbchip (22 px, 3 px Petrol-Rand) am Anlagennamen, im Seitenkopf und im Druckkopf.
- Ring statt Orange: Auswahl und Hervorhebung zeigt ein Petrol-Ring (3–4 px, dann ein weißer Spalt, dann der Ball).
- Ball nie nur über Farbe: Jeder Ball bekommt zusätzlich einen Buchstaben (Anfangsbuchstabe des Ballnamens, überschreibbar mit dem Feld
kuerzel).
Zwei Beispielanlagen
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.
--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
So: Auswahl = Petrol-Ring. Der Ball bleibt in Echtfarbe, mit Buchstabe.
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
| Was | Wo |
|---|---|
| Tokens (Farben, Radien, Raster) | static/css/tokens.css |
| Komponenten-CSS, Druck | static/css/style.css |
| Schriften (self-hosted) | static/fonts/, geholt mit scripts/fonts-holen.sh; @font-face in tokens.css |
| Logo | layouts/partials/logo-mark.html (hell, invertiert, favicon), logo.html (Lockup) |
| Favicon und Share-Bild | static/favicon.svg, favicon-32.png, apple-touch-icon.png, static/images/share.png; erzeugt mit scripts/logo-assets.py |
| Komponenten | layouts/partials/: plan-karte, farbchip, ball-chip, button, kachel, bahn-plan |
| Anlagenfarben | Front Matter der Anlage: farben: chip, bahn, linie (und die Rollen flaeche, bande …) |
| Ballkürzel | Optionales Feld kuerzel der Ballseite, sonst Anfangsbuchstabe des Balltitels (ball-kuerzel.html) |
| Druckkopf und Fußzeile | layouts/partials/druckrahmen.html, CSS im @media print von style.css |
| Diese Seite | content/styleguide/_index.md, layouts/styleguide/list.html |