Skip to content

Programmheft: visuelle Neugestaltung der gesamten Plattform - #319

Merged
nichtLehdev merged 83 commits into
release/1.0.0from
feat/programmheft-grundlagen
Sep 17, 2026
Merged

nichtLehdev merged 83 commits into
release/1.0.0from
feat/programmheft-grundlagen

Conversation

@nichtLehdev

@nichtLehdev nichtLehdev commented Sep 17, 2026

Copy link
Copy Markdown
Owner

Ersetzt die karten- und schattenlastige Gestaltung durch die Programmheft-Welt: weißes Papier, orange Druckfelder, Tinte, Haarlinien, Archivo in schmaler und halbschmaler Achse. Keine Rundungen, keine Schlagschatten, keine verschachtelten Rahmen.

Inhalt, Texte, Informationsarchitektur und Funktion bleiben — es ändert sich die Darstellung. Ausnahmen, die darüber hinausgehen, sind unten einzeln aufgeführt.

Umfang

76 Commits, 318 Dateien, +27.982/−28.794 Zeilen.

Bereich Dateien
Geteilte Bausteine 113
Dashboard-Seiten 80
Öffentliche Seiten 53
Dashboard-Bausteine 34
Server (E-Mail-Vorlagen) 24
Spiele, lib, styles 10

Neu entstanden ist eine Bausteinschicht unter _components/programmheft/ (Seitenkopf, Abschnitte, Etiketten, Personenzeilen, Wegelisten, Bezirksmarke). Entfallen sind die alten Karten-, Karussell- und Kopfzeilen-Bausteine.

Über die Gestaltung hinaus

Diese Änderungen betreffen Verhalten, nicht nur Aussehen, und sollten beim Prüfen besondere Aufmerksamkeit bekommen:

  • Sicherheit: USE_PROFILES stand neben ALLOWED_TAGS im HTML-Filter und machte beide Listen wirkungslos. <p style="position:fixed;width:100vw;height:100vh"> und <form><input type="password"> kamen unverändert durch — zusammen ein bildschirmfüllendes Passwortfeld auf eigener Domain, verfasst im Beitragseditor. Skripte waren nie betroffen, und es braucht ein Redaktionskonto. (83e2c71)
  • Kurse: Die Veröffentlichungswahl beim Anlegen erreichte den Server nie — jeder Kurs landete auf PENDING, obwohl die Oberfläche „wird sofort angezeigt" versprach. Und ein gelöschter Kurs riss seine ausgestellten Rechnungen per Kaskade mit, entgegen der Zusage „bleibt im Archiv erhalten". (3d1d8a6)
  • Artikel: Bilder lassen sich jetzt in Breite und Ausrichtung setzen, mit einrastenden Ziehgriffen; das Titelbild steht im Text statt als Block darüber. Vorbild ist das Blechblatt. (f3d7f99, 19ec1dd, 9829989, deffe0d)
  • Barrierefreiheit: zugängliche Namen für Auswahlfelder, Sprunglinks, 44px-Klickziele, Bezirksfarben als Markierung statt als Schriftgrund. (7ad3d8a, aca562f)

Geprüft

  • jest: 54 Suiten, 634 Tests grün
  • next build: erfolgreich
  • tsc --noEmit, eslint, prettier: sauber
  • axe-core über acht Dashboard- und sieben öffentliche Seiten, Chromium und WebKit, hell und dunkel: 0 Verstöße, inklusive color-contrast
  • Kein waagerechter Überlauf bei 390, 768, 1024 und 1440px

Bewusst nicht enthalten

  • Die Spiele unter /spiele bleiben im alten Design — eigener Durchgang, folgt separat.
  • Die Instagram-Exportvorlagen: ihre Farben gehören ins exportierte Bild, nicht ins Interface.
  • Rot bleibt Signalfarbe für Fehler, Absagen und Löschungen.

Offen, zur Entscheidung

  • dashboard/media/page.tsx sagt „Ausstehend"/„Freigegeben", die Dialoge daneben „Zur Prüfung"/„Veröffentlicht"
  • Platzhalterdaten in der Datenbank: fünf Personen mit @example.de, Testkurs test-2026 öffentlich, motto = "unchanged" auf zwei Kursen
  • DESIGN.md beschreibt den Stand vom 15.09. und kennt das Dashboard noch nicht

Archivo mit Breitenachse als Variable für die öffentliche Website (das
Dashboard bleibt bei Inter). Neue Farb-Tokens (Tinte, Papier,
Messing-Tinte, Haarlinie, Nachtdruck), schmale Schnitte, Satzbreite,
Füllzeile, Fokus- und Markierungsfarben sowie reduzierte Bewegung.
Gleiche Links, Texte und Logik; neue Darstellung ohne Rundungen und
Schatten, 44px-Bedienflächen, Menüs mit Tintenrahmen, Tastaturfokus für
die Aufklappmenüs. Banner mit lesbarem Kontrast (Tinte auf Orange).
Titelblatt mit Bildstrecke (anhaltbar, ohne Autoplay bei reduzierter
Bewegung), „Kommende Termine“ als Programmliste mit offenen Anmeldungen
vorn (oranges Datumsfeld, Frist, Anmelden), Aktuelles als Druckspalten
mit Logo ohne Titelbild, Register „Beliebte Seiten & Partner“ und
Schlussseite auf Papier. Inhalte und Texte bleiben unverändert.
PRODUCT.md hält Zielgruppen, Ziele, Markenvorgaben und belegte Inhalte
fest; DESIGN.md beschreibt das Programmheft-Designsystem der
öffentlichen Website (Tokens, Typografie, Regeln, Komponenten) als
Grundlage für den Rollout auf die übrigen Seiten.
„Lust auf Posaunenchor?“ steht als bewusster Blickfang auf voller oranger
Fläche mit der Schaltfläche „Mehr erfahren“. DESIGN.md beschreibt den
Schlussaufruf als eigene Komponente und passt Do/Don't daran an.
…itive

Die Startseite setzt sich jetzt aus wiederverwendbaren Programmheft-
Bausteinen zusammen: Köpfe (Heading, SectionHead, ArrowLink), Abschnitt
(PageSection), Wegzeilen und Registerzeilen (WayList, WayRow), Programm-
zeilen mit Datumsslot und offener Anmeldung (ProgrammeList, ProgrammeRow,
DateSlot), Bezirksmarke, Beitragsspalten (NewsColumns), Schaltflächen
(ButtonLink) und Schlussaufruf (ClosingCall).

Die Startseite rendert unverändert: alle Blöcke stehen pixelgenau an
derselben Position. Neu im CSS sind die blaue Zeilenfüllung für
Förderverein-Zeilen und der Fließtext-Link `.link-ink`.
PublicPage setzt Innenseiten jetzt im Programmheft: ein Seitenkopf auf
Papier (PageHead) mit Brotkrumen, genau einem <h1>, Satzstrich und
Leitsatz; die klebende zweite Titelleiste (PageHeader) entfällt. Die
Seite steht vollständig in Archivo, auch Abschnitte, die noch nicht
übertragen sind.

- `color` entfällt: Bezirksfarben färben keine Köpfe mehr, der
  Förderverein setzt `tone="foerderverein"` (blauer Satzstrich).
- Öffentliche Varianten statt ui/card, ui/badge, ui/alert, die dem
  Dashboard bleiben: Panel, Tag, Note.
- PersonRow/PersonList ersetzen PeopleCard (Förderverein, Posaunenrat).
- Wegzeilen ersetzen ParticipationCard auf Über uns, Spiele, Materialien.
- Kopfzeilen der Kurs-, Termin-, Anmelde- und Ensembleseiten mit
  Etiketten und Bezirksmarke statt weißer Pillen; der negative Abstand
  unter dem alten Kopf entfällt.
- Der Ladezustand von /termine nutzt denselben Seitenkopf.
Alle Texte bleiben, nur die Darstellung wechselt: Einstiegsmöglichkeiten
als Registerzeilen, Förderverein mit blauem Satzstrich, Fakten in
Haarlinienspalten und blau füllenden Wegzeilen, „Warum Posaunenchor?“
als Merkpunkte, Mitgliedsbeiträge im Tabellensatz, Downloads als
Wegzeilen, Ehrungen und Versicherungsfrist als Hinweise, der
Schadensfall als Kasten mit Unterlagenliste. „Noch Fragen?“ schließt die
Seite als oranger Schlussaufruf; der Newsletter steht deshalb davor.

Neue Bausteine: PointList (Merkpunkte) und ValueTable (Beträge).
ParticipationCard und SectionHeader werden nicht mehr gebraucht.
- Beta-Etikett ohne Sperrung und Versalien (Tag); Banner-Varianten
  Warnung und Wartung als Tintenfläche, das ungenutzte „success“ entfällt.
- themeColor im Dunkelmodus auf den Nachtgrund #141517.
- Ungenutzte animate-homepage-*-Animationen entfernt.
- hero-carousel.tsx und upcoming-courses-carousel.tsx gelöscht.
Kurs-, Termin- und Anmeldeseite zeigten die Datenbankwerte (LEHRGANG, KONZERT) statt der Bezeichnungen, die auch das Programm der Startseite nutzt.
…tmachen

- Förderverein-Abschnitt als volle Druckfläche in Fördervereinsblau
  (PageSection surface="foerderverein", `.print-field`: Tinte auch im
  Nachtdruck, Zeilen füllen sich papierweiß).
- Fotofeld neben „Warum Posaunenchor?“ mit Bildnachweis; bis eigene
  Fotos gepflegt sind, aus der Bildstrecke der Startseite.
- Ein Strichsystem: der Seitenkopf schließt vollbreit wie die Abschnitte
  (Nacht: Nachtlinie).
- Längere Vorspanne stehen unter dem Titel statt in der schmalen Spalte.
- Kasten im Nachtdruck gleich gebaut; Ehrungen ohne zweiten Kasten.
- Etiketten sind nie umrandet: Art, Dauer, Ort und Bezirk im kompakten
  Kopf als Meta-Text, der Outline-Ton von Tag entfällt.
- Mitmachen: 5/7-Raster, Tabelle und Hinweis über die volle Spalte,
  bewusster Umbruch „Einstiegs-“.
Auf Wunsch des Eigentümers alternieren die Köpfe ab 64rem wie linke und
rechte Heftseiten (neuer Baustein `Split`, 4/12 Kopf neben 8/12 Inhalt,
Kopf im DOM immer zuerst). Das Raster kehrt damit zu 4/8 zurück; das
Fotofeld neben „Warum Posaunenchor?“ entfällt.
DESIGN.md beschreibt jetzt, was diese Sitzung gebaut hat: Seitenkopf der
Innenseiten (normal und kompakt), Abschnitt mit Split und blauer
Druckfläche, Merkpunkte, Beträge, Personenzeile, Etikett, Hinweis, Kasten,
wiederverwendbarer Schlussaufruf, Banner-Varianten, Schaltflächen auf
Papier und Textlinks im Fließtext. Neu sind die One Field Rule (höchstens
eine blaue Förderverein-Fläche neben Titelblatt und Schlussaufruf) sowie
Do's und Don'ts zu Köpfen, Etiketten und Kästen. Der veraltete Hinweis auf
die alten Bausteine ist ersetzt.
Die Einstiegsseite richtet sich an Neue: Jungbläserarbeit rückt nach
vorn, „Warum Posaunenchor?“ steht vor dem Förderverein, Anrede
durchgehend „du“, aktuelle Förderverein-Angaben. Mitgliedschaft,
Beiträge, Ehrungen und Instrumentenversicherung stehen wörtlich
unverändert auf /mitmachen/mitgliedschaft (Anrede „Sie“), verlinkt aus
Abschnitt, Menü und Fußzeile und aufgenommen in Sitemap und Suche.
Geschwister-Ermäßigung sind 20 % der Lehrgangskosten ab dem zweiten
Kind, die Geschenk-CD gibt es für alle Neumitglieder. Die abgelaufenen
Sonderaktionen 2025 samt Verlosung entfallen.
Beitragsliste und ähnliche Beiträge als Druckspalten mit Haarlinien
statt Karten, Filterleiste flach, Beiträge ohne Titelbild zeigen das
Logo auf Tinte. Beitragsseite mit ruhigem Kopf, Meta-Zeile und
Bildfeld im Text; Lightbox ohne Rundungen und Weichzeichner. Suche
erstmals mit Seitenrahmen und nach Art gruppierten Trefferzeilen.
Newsletter-Seiten als Innenseiten mit flachen Eingabefeldern.
Texte, Filter, RSS und Double-Opt-in unverändert.
…rogrammheft

Downloads stehen als Wegzeilen mit Dateimeta, Auszug und Etiketten im
Tabellensatz statt in Karten. Die Blechblatt-Ausgaben laufen zweispaltig
als Haarlinienliste, die Bläserhefte als Druckspalten mit Cover.

Die Cover liegen alle im Querformat 3:2 vor. Bisher streckte die Flex-Zeile
die Bildspalte auf Texthoehe, wodurch object-cover nur einen senkrechten
Streifen aus der Bildmitte zeigte. Das Feld uebernimmt jetzt das echte
Verhaeltnis und steht mit self-start oben.
…Programmheft

Sechs Seiten verlieren Karten, Schatten und Icon-Kreise und laufen im
Haarlinien- und Tabellensatz. Bezirksfarben stehen nur noch als kleine
Marker vor dem Namen, nicht mehr als Farbfläche hinter Karten und Etiketten;
die farbigen Kopfstreifen im Posaunenrat entfallen.

Die Kernzahlen stehen als Werttabelle, die Zeitleiste tabellarisch mit
Jahreszahl in Ziffernbreite. Auf der Strukturseite bleibt das blaue
Fördervereinsfeld die einzige volle Druckfläche vor dem Schlussaufruf.

Neu ist person-contact-row als Variante von PersonRow für Einträge mit
Telefon und Kurzbiografie. Lange Komposita in den schmalen Kopfspalten
werden per weichem Trennstrich getrennt, der Ensemblename aus der Datenbank
per automatischer Silbentrennung.
Kontakt, Impressum, Datenschutz, Lizenzen, Prävention, Feedback, Offline-
und Wartungsseite laufen im Haarliniensatz mit Abschnittsköpfen in der
Kopfspalte. Der Wortlaut der Rechtstexte bleibt unverändert.

Neu ist programmheft/field mit Beschriftung, Hinweis, Fehlermeldung und
einem eckigen Kontrollkästchen. Das Kontaktformular nutzt jetzt ein
natives Auswahlfeld statt der gerundeten Dashboard-Komponente, weil cn()
in diesem Projekt Klassen nur aneinanderhängt und deren eingebackene
Rundung deshalb nicht zuverlässig überschrieben werden konnte.
Die drei geteilten Oberflächen waren die letzten Flächen der alten Welt:
gerundete Tafeln mit Schlagschatten, Grau-Palette und bunte Typ-Pillen.
Sie sind jetzt eckige Tafeln mit Tintenrand und Haarlinienzeilen.

Im Suchfenster entfallen die farbigen Icon-Kacheln je Ergebnisart; die Art
steht als Wort in der Metazeile, davor ein blankes Zeichen in Tinte.

Der Zähler an der Glocke stand als Papierschrift auf Orange und erreichte
damit nur 1,99:1. Er steht jetzt in Tinte auf Orange. Der QR-Code wird in
Tinte statt in Graublau gezeichnet, seine Logofläche ist nicht mehr gerundet.
…ndnotiz

Der Abonnieren-Knopf war deaktiviert, solange die Einwilligung fehlte, und
stand damit von der ersten Sekunde an grau da, ohne zu sagen was fehlt. Er
ist jetzt immer bedienbar; das required am Kästchen lässt den Browser genau
dorthin springen. Deaktiviert wird nur noch während des Absendens.

Der Hinweis zur Abmeldung steht neben dem Formular statt darunter, damit
die rechte Seitenhälfte nicht leer bleibt.
Die Terminliste läuft als Programmzeilen mit Datumsfeld, Monatsköpfen und
Haarlinien statt als Kartenraster; bei offener Anmeldung steht das Datum im
orangen Feld mit Status und Anmelde-Schaltfläche. Kurs- und Termindetails
nutzen Werttabellen, Wegzeilen und je eine Tafel für den Anmeldeblock.

Der Anzahlungsbereich stand in verschachtelten Bernstein-Kästen und liegt
jetzt als eine Tafel mit einer durchgehenden Werttabelle vor: Status,
Beträge, IBAN, BIC und Verwendungszweck in einer Spalte.

Drei Stellen formatierten Geld mit toFixed(2) statt über formatEuro und
zeigten damit Punkt statt Komma; sie sind korrigiert. Die Überweisungsdaten
brachen auf schmalen Geräten aus der Zeile, weil Werttabellen ihre Werte
nicht umbrechen — dort brechen sie jetzt um.

Die Bezirksfarben auf den Kalendermarken bleiben erhalten: Der Kalender ist
eine Datenansicht, in der die Farbe unterscheidet und nicht schmückt.
Aus Icon-Kreis-Karten werden Punktlisten und Wegzeilen, aus farbigen
Aufruf-Balken der Schlussaufruf am Seitenende. Der Erfahrungsbericht auf
der Ehrenamtsseite steht als typografisches Zitat mit Tintenstrich statt
in einer Karte.

Der Hinweis „Wichtig" auf der Bildungsseite bekommt einen Tintenrahmen
statt einer orangen Füllung: Die Seite trägt bereits das blaue
Fördervereinsfeld und den orangen Schlussaufruf, und eine dritte Farbfläche
direkt unter dem Titel war lauter als die Überschrift. Damit liegt die Seite
beim selben Maß wie /mitmachen und /ueber-uns/struktur.
titleAs erlaubte nur p, h3 und h4. Die Bildungsseite setzt den Hinweis
„Wichtig" als eigenen Abschnitt und übergab h2, was die Typprüfung
zurückwies. Der Fehler ist mit de227c7 hereingekommen, weil meine
Typprüfungen den Pfad ausgeblendet hatten, solange dort ein Agent arbeitete.
Die Chorsuche läuft als Registerzeilen mit eckigen Filterfeldern, eckiger
Blätterung und einer Umschaltung zwischen Liste und Karte; die Karte steht
im einzigen Kasten der Seite. Das Bezirksverzeichnis wird zur durchlaufenden
Liste, die Bezirksfarbe steht nur noch als Marker vor dem Namen statt als
Kopffläche einer Karte.

Auf der Bezirksseite stand der Schlussaufruf in Papierschrift auf Orange und
erreichte damit nur 1,99:1; er nutzt jetzt den Schlussaufruf-Baustein mit
Tinte. Der Hinweis „Posaunenchor in der Nähe suchen" bekommt einen
Tintenrahmen statt einer orangen Füllung, weil der Schlussaufruf am
Seitenende dieselbe Bitte stellt und der einzige Blickfang bleiben soll.

Die Bezirksfarbe wurde als bg-district-${n} gesetzt. Tailwind kann aus einem
Laufzeit-Template keine Klasse erzeugen, die Einfärbung war also vermutlich
schon vorher wirkungslos; sie läuft jetzt über getDistrictColor.
Beitragsspalten: Die Innenabstände hingen am laufenden Index, wodurch die
mittleren Spalten schmaler waren. Bei festem Seitenverhältnis wurden ihre
Bildfelder niedriger und die Überschriften standen versetzt. Jetzt hat jede
Spalte denselben Abstand, das Raster wird um diesen Betrag nach links
gezogen. Gemessen: Bildhöhen gleich, Überschriften innerhalb von 1 px.

Bläserhefte: Das Bildfeld war als Flex-Kind mit w-full und shrink-0
mehrdeutig. WebKit gab ihm die ganze Zeilenbreite und quetschte den Text auf
ein Wort pro Zeile — in Safari war die Seite dadurch unbrauchbar. Jetzt eine
feste Rasterspalte, in echtem WebKit nachgeprüft. Die Einträge laufen über
die volle Satzbreite, weil sie mit Cover und Text schon zwei Spalten
mitbringen und daneben eine dritte, leere Spalte stand.

Auswahlfelder: Tailwinds appearance-none schreibt kein -webkit-Präfix,
deshalb stand in Safari zusätzlich das native Dreieck neben unserem eigenen.

Etiketten: Die Zeilenfüllung färbte auch ihre Schrift in Tinte — auf einem
tintefarbenen Etikett war sie unlesbar. Sie sind jetzt ausgenommen.

Klebende Filterleisten: Ihr Abstand war mit 112/144 px fest verdrahtet, der
Höhe der alten Kopfzeile samt Kontextleiste. Die Navigation ist heute 64
bzw. 80 px hoch, darüber blieb eine Lücke. Gemessen wird jetzt die
Navigation selbst.

Weniger Linien: Kalender und Blechblatt setzten ihre inneren Trenner in
2 px, wodurch sich beim Öffnen fünf schwere Linien stapelten; sie sind
jetzt Haarlinien. Auf der Bildungsseite teilen sich Hinweis und erster
Abschnitt ein Band statt drei Linien hintereinander zu erzeugen.

Abstand unter dem Seitenkopf: flush="top" nahm Spiele, Newsletter und den
Über-uns-Unterseiten die Luft zum ersten Abschnitt.
Die Seite war beim Ausrollen übersehen worden und trug als letzte
öffentliche Seite noch Rundungen, Schatten und Icon-Kacheln. Sie läuft
jetzt im Haarliniensatz mit dem blauen Fördervereinsfeld als einziger
Farbfläche vor dem Schlussaufruf; die Bankverbindung steht im einen
erlaubten Kasten der Seite. Wortlaut und Zahlen unverändert.
Die Umstellung hatte die Fotos auf kleine runde Avatare verkleinert. Sie
stehen jetzt wieder als großzügige Porträts im festen Seitenverhältnis 3:4,
rechteckig und ohne Schatten, mit Name, Amt und Kontakt im Haarliniensatz
darunter. Fehlt ein Foto, steht dort das Logo auf Tintenfeld wie bei
Meldungen ohne Titelbild, damit die Reihe gleich aufgebaut bleibt.
Beide Blöcke standen untereinander; nebeneinander waren sie praktischer.
Sie stehen jetzt in zwei Spalten mit Haarlinie dazwischen und stapeln sich
unterhalb von 640px wieder. Wortlaut unverändert.

Hinweis: Der Dev-Server liefert für diese Route ein veraltetes Kompilat und
zeigt die Änderung erst nach einem Neustart — Datei, Lint und Typprüfung
sind sauber, live gesehen habe ich sie nicht.
Beim Scrollen verlor man die Seite aus dem Blick: Die alte Kopfzeile zeigte
sie an, die neue nicht mehr. Die Leiste trägt jetzt links den Seitentitel
wie ein Kolumnentitel im Heft, daneben Ansicht, Trefferzahl und Aktionen.
Sie füllt damit genau den Platz, der vorher als Lücke unter der Navigation
stand.

Beide Leisten sprechen jetzt dieselbe Sprache: Haarlinie statt 2px-Strich
unten, gleicher Aufbau von Titel, Zahl und Aktionen. Vorher war die eine
schwer abgesetzt, die andere fein.
Die Seite trug als einzige noch einen eigenen Rahmen mit fuenfteiligem,
handgeschriebenem Brotkrumenpfad — rund fuenfzig Zeilen JSX, die
DashboardPage als Datenfeld entgegennimmt. Der abgebrochene Agent hatte die
Migration begonnen und den Import bereits gesetzt, kam aber nicht mehr zum
Umbau; ich hatte danach nur umgefaerbt, wodurch der Import verwaist blieb
und ESLint zu Recht anschlug.

Der blaue Hinweiskasten weicht einer Haarlinie: Er erklaert eine Ausnahme
(Nachtragen nach Anmeldeschluss), er warnt nicht — eine eigene Signalfarbe
braucht er dafuer nicht.

Damit ist die letzte Seite erledigt, die der Abbruch durch die
Sitzungsgrenze unfertig hinterlassen hatte.
Select trug role="combobox" — dort zaehlt sichtbarer Text nicht als Name.
aria-label wurde vom Typ zwar akzeptiert, aber nie durchgereicht: Der
Compiler sagte ja, zur Laufzeit fiel es auf den Boden. Deshalb konnten die
Aufrufstellen den Fehler gar nicht selbst beheben. Jetzt durchgereicht, mit
dem Platzhalter als Rueckfall — er benennt den Zweck und wechselt im
Gegensatz zum Auswahltext nicht mit der Auswahl. Wer ein id mitgibt,
behaelt seine <label for>-Kopplung; ein aria-label wuerde deren sichtbaren
Text ueberschreiben.

Vier Kategorie-Auswahlen und drei Datums-, Zeit- und Dateifelder an ihre
bereits sichtbaren Label gekoppelt.

Zwei Sprunglinks: einer ueberspringt die Navigation, einer die
Seitenleiste. Der Weg zum Dashboard-Inhalt kostete 29 Tabulatorschritte,
jetzt zwei. Verschoben statt sr-only, weil sr-only und not-sr-only sich um
dieselbe position-Eigenschaft streiten.

Seitenleisten-Links waren 36 und 32 Pixel hoch — ausgerechnet in der Datei,
die der Kontrakt als Vorlage nennt. Jetzt 44.

Spiel-Schrittanzeiger war weiss auf Orange (1,99:1), jetzt Tinte auf Orange
(8,46:1).

axe ueber acht Dashboard-Seiten in Chromium und WebKit: 0 Verstoesse,
vorher 33 button-name und 16 label.
"Posaunenenchoere" auf /ueber-uns.

/mitmachen/bildung nannte 25 Euro pro weiterem Kind, /mitmachen und
/foerderverein nennen 20 Prozent — und die Berechnung in
lib/sibling-discount.ts rechnet mit 0.2. Drei Quellen gegen eine; der
Festbetrag war veraltet.
Gemessen: Weiss traegt auf keiner der dreizehn Bezirksfarben (1,81:1 bis
4,47:1). Tinte reicht bei Kleintext auf neun von dreizehn, schlechtester
Wert 3,78:1. Es gibt also keine Schriftfarbe, die auf allen dreizehn
funktioniert — deshalb steht die Farbe jetzt als Quadrat neben der Schrift,
wie BezirkLabel und die Termin- und Kursliste es laengst machen.

Ausnahme mit Ansage: Die Bezirkskachel auf der Detailseite behaelt ihre
Flaeche. 24px fett ist WCAG-Grosstext mit Schwelle 3:1, dort traegt Tinte
auf allen dreizehn.

Auswahlchor-Platzhalter trug ein weisses Zeichen auf Druckorange (1,99:1),
jetzt Tinte.

Geschwisterrabatt-Pille war pastellgruen bzw. pastellgelb mit voller
Rundung; jetzt Tag. Berechtigt wird gefuellt, nicht berechtigt umrandet —
gefuellt heisst: Das musst du sehen.
article-content.css gestaltet den gespeicherten Beitrags-HTML an drei
Stellen zugleich: oeffentliche Beitragsansicht, Dashboard-Vorschau und
Editorflaeche. Sie war vollstaendig im alten Design und dem Audit entgangen,
weil der gerenderte Dashboard-Seiten geprueft hat, nicht Artikelkoerper.

Gemessen vorher: Links standen auf #faa619 — Druckorange als Schriftfarbe
auf Papier, 1,99:1. In allen drei veroeffentlichten Beitraegen mit Links.
Jetzt primary-ink: 5,26:1 hell, 9,16:1 dunkel. 10 von 10 Links bestehen, in
Chromium und WebKit.

Links sind zusaetzlich dauerhaft unterstrichen. Vorher waren sie im
Fliesstext allein an der Farbe zu erkennen — das reicht fuer farbfehlsichtige
Leser nicht (WCAG 1.4.1).

code stand auf Magenta #d946ef (3,46:1), jetzt Tinte auf getoentem Grund.

Rundungen und Schlagschatten an Bildern, Code und Tabellen entfernt: von je
0 gemessen, vorher auf jedem Bild. Tabellenkoepfe tragen jetzt eine Linie
statt einer Flaeche, Trennlinien sind Haarlinien.

Die Graustufen des Fliesstexts waren uebrigens KEIN Kontrastfehler (7,56:1)
— das war eine Stilfrage. Nur Links und code sind wirklich durchgefallen.

Das !important bleibt mit Absicht: Im gespeicherten HTML stehen noch
Tailwind-Klassen aus der TipTap-Konfiguration. Weil dieses Stylesheet sie
uebersteuert, wirkt die Korrektur auch auf Altbeitraege — sonst muesste
jeder einzeln neu gespeichert werden.
Die Funktion verspricht dem Namen nach, die Schriftfarbe auf einer
Bezirksfarbe zu waehlen, kann das Kontrastproblem aber nicht loesen. Sie
rechnet mit wahrnehmungsbasierter Helligkeit statt WCAG-Relativluminanz,
und selbst richtig gerechnet decken Schwarz und Weiss zusammen die
dreizehn Farben nicht ab.

Nachgemessen: Weiss faellt auf allen dreizehn durch, Tinte bei Kleintext
auf vier, diese Funktion ebenfalls auf vier (Bezirk 1, 3, 5, 9).

Sie ist derzeit harmlos, weil ihr einziger Aufrufer post-card.tsx selbst
von niemandem importiert wird. Der Kommentar steht da, damit der Naechste
nicht danach greift — der Name laedt dazu ein.

getDistrictColorLight hat gar keinen Aufrufer.
…eschlossen

Zwei Regeln, ueber den Dashboard-Baum durchgezogen.

Rohe Gruen-, Bernstein- und Gelbtoene: 106 Fundstellen auf 3. Zustaende
tragen jetzt Tag mit vorhandenem Ton, Erfolgs- und Hinweiskaesten wurden zu
Tinte auf Papier an einer Haarlinie, bunte Kennzahlen zu neutraler Schrift.
Rot bleibt ueberall Rot — Fehler, Absagen und Loeschungen behalten ihre
Signalfarbe.

Die drei verbleibenden stehen mit Absicht: zwei kategoriale Punkte einer
Mitgliedschaftslegende, in der Blau, Lila und Markenfarbe danebenliegen —
nur den gruenen zu aendern haette die Kategorie unlesbar gemacht, ohne dass
irgendetwas einheitlicher wird. Dazu ein Bernstein-Knopf fuer "Absagen",
der einen eigenen Kommentar traegt: folgenreich, aber keine Loeschung.

text-dark ohne dark:-Gegenstueck: 38 auf 15, und die 15 liegen
ausschliesslich in den Spielen und den Instagram-Vorlagen, die beide
bewusst ausgenommen sind. --color-dark ist #58595b und hat keine
Dunkelmodus-Ueberschreibung; auf Nachtgrund waren das 2,58:1. Jetzt
night-muted mit 7,68:1.

Sieben Stellen auf der Anmeldungs-Detailseite hatte ich selbst gesperrt,
weil ich dort kurz zuvor gearbeitet hatte — hier nachgezogen. Die
Rabattzeile wird dabei bewusst KEIN Akzent: Sie steht zwischen
Zwischensumme und Gesamtbetrag, und der Gesamtbetrag traegt den Akzent
bereits. Zwei gleich gewichtete Zeilen haetten die Endsumme entwertet.

Geprueft: prettier, eslint und tsc je Exit 0 ueber alle geaenderten
Dateien; axe ueber acht Dashboard-Seiten in Chromium und WebKit 0
Verstoesse; Anmeldungs-Detailseite rendert hell und dunkel ohne Konsolen-
fehler und ohne horizontalen Ueberlauf.
In der geteilten Huelle brach der Kopf ab sm nicht um. Der Titel trug
min-w-0 und durfte auf null schrumpfen, die Knopfgruppe trug shrink-0 und
schrumpfte nie — weshalb ihr eigenes flex-wrap nie ausloeste und sie stur
bei voller Breite blieb. Das Defizit musste also allein der Titel tragen.

Reproduziert ohne Datenaenderung, vierter Knopf nur im DOM eingeblendet:
bei 1024px fiel "Rechnungen" von 190x35px auf 0x350px — null Breite, zehn
Zeilen hoch, buchstabenweise untereinander. Bei 1100px 59x105px. Ab 1280px
unauffaellig. Das deckt sich mit dem Befund aus der Inhaltspruefung, der
allerdings die Rechnungsseite als Ursache vermutete; sie liegt in der
Huelle und betraf jede Dashboard-Seite mit vielen Aktionen.

Nachher an derselben Stelle: 190x35px, Knopfgruppe in zwei Zeilen, kein
Ueberlauf.

Regression geprueft ueber 13 Dashboard-Seiten bei 390, 768, 1024 und
1440px in Chromium und WebKit: 104 Messungen, 0 gequetschte Titel, 0
horizontale Ueberlaeufe. axe ueber acht Seiten weiterhin 0 Verstoesse.
Zwei Fehler, beide im Kurs-Router.

Die Veroeffentlichungswahl beim Anlegen war Dekoration. Das Eingabeschema
von courses.create kannte kein status-Feld, der Client schickte keins, jeder
Kurs landete auf dem Prisma-Vorgabewert PENDING — auch wenn die Oberflaeche
ausdruecklich "Der Kurs wird sofort auf der Webseite angezeigt" versprach.
Termine fuehrten das Feld laengst; hier nachgezogen, inklusive der Pruefung,
dass nur mit courses.approve auf APPROVED gesetzt werden darf. Ohne sie
koennte sich jeder Autor seine eigenen Kurse freigeben, sobald das Feld
ueberhaupt ankommt.

Invoice.course stand auf onDelete: Cascade. Ein geloeschter Kurs riss seine
Rechnungen mit, samt fortlaufender Nummer, archiviertem PDF und
Veroeffentlichungsdatum. Das widersprach dem Schema an anderer Stelle:
Invoice.registration steht auf SetNull, ausdruecklich weil "die Rechnung
aufbewahrungspflichtig" ist. Jetzt weisen delete und bulkDelete das Loeschen
ab, solange Rechnungen mit vergebener Nummer existieren; Entwuerfe haben
keine Nummer und stehen nicht im Weg. bulkDelete braucht dieselbe Sperre,
sonst waere sie ueber die Mehrfachauswahl zu umgehen.

Der Loeschdialog nennt die Regel jetzt vorher, nicht erst als Fehlermeldung
danach.

Geprueft gegen die echten Prozeduren ueber HTTP, mit Wegwerf-Daten und
synthetischer Rechnungsnummer ausserhalb des Zaehlers: DRAFT, PENDING und
APPROVED kommen alle drei an; Kurs ohne Rechnung loescht; nummerierte
Rechnung sperrt mit PRECONDITION_FAILED und der Kurs steht danach noch;
reiner Entwurf sperrt nicht; bulkDelete sperrt ebenso. Kein Rueckstand, die
vier echten Rechnungen unveraendert.
USE_PROFILES stand neben ALLOWED_TAGS. DOMPurify *erweitert* damit die
Profilliste, statt sie zu ersetzen — die beiden Listen darunter waren
praktisch wirkungslos. Nachgemessen kamen `<p style="position:fixed;
width:100vw;height:100vh">` und `<form><input type="password">`
unveraendert durch. Zusammen ist das ein bildschirmfuellendes Passwortfeld
auf eigener Domain, verfasst im Beitragseditor — genau die Ueberlagerung,
vor der der Kommentar an ALLOWED_ATTR warnte. Die Absicht stand also da,
nur griff sie nicht.

Skripte waren nie betroffen: script, onerror, javascript:, iframe und svg
wurden auch vorher entfernt. Es braucht ein Redaktionskonto, anonym ist
nichts moeglich.

figure und figcaption jetzt ausdruecklich erlaubt: posts.ts haengt
Bildnachweise serverseitig an, *bevor* gefiltert wird — ohne sie haette das
Zuschnueren genau diese Nachweise wieder ausgeraeumt.

data-copyright und data-creator werden benannt durchgelassen; der
Leuchtkasten der Beitragsansicht liest sie aus, sie wurden aber entfernt.
Dazu ADD_URI_SAFE_ATTR, sonst haette die Erlaubnis nur halb gewirkt:
DOMPurify verwirft Attributwerte mit Doppelpunkt als moegliche URIs.
Gemessen flog `data-creator="Foto: A. B."` raus, `"Foto A B"` blieb — und
„Foto: Name" ist genau das Format, das posts.ts erzeugt. Die pauschale
data-Erlaubnis bleibt aus.

Geprueft: 13 Faelle, keine Fehlschlaege. style, form/input, script,
onerror, javascript: in href und src, iframe und fremde data-Attribute
fallen; class, width/height, figure/figcaption und die zwei benannten
Attribute bleiben; das echte posts.ts-Markup geht unveraendert durch.
Bestandsdaten sind unbelastet: von acht Beitraegen enthaelt keiner style,
form, input, iframe oder data-Attribute, Termine und Kurse ebenso wenig.
Alle acht Nutzer von sanitizeHtml geprueft, auch der Mailpfad — der
filtert Markdown-erzeugtes HTML, das kein style enthaelt.
Grundlage fuer den Magazinsatz. Das Lesemass ist von der Huelle in
post-detail-view auf den Artikelkoerper selbst gewandert — solange es aussen
hing, konnte kein Bild breiter werden als der Text.

Vier Breiten (schmal 45%, halb 60%, breit, randlos) und drei Ausrichtungen
(links, rechts, mittig) als Klassen. Klassen deshalb, weil der Filter
Inline-CSS verwirft; class, width und height ueberleben, style nicht. Ein
festes Vokabular passt ohnehin besser zum Heft als freies Skalieren.

Bewusst kein Raster mit benannten Spalten: TipTap fuegt Bilder als
Blockknoten direkt unter der Wurzel ein, sie waeren damit Rasterelemente —
und auf Rasterelementen wird float ignoriert, der Textumfluss also
unmoeglich. Im Blockfluss traegt beides.

`randlos` meint den Satzspiegel, nicht 100vw: Ein 100vw breites Element in
einem zentrierten Container erzeugt bei sichtbarer Scrollleiste waagerechten
Ueberlauf.

Ausbruch nur mit `--seite`. Die Ausbruchsbreiten rechnen mit 100vw, und das
Fenster ist in der Editorflaeche genauso breit wie auf der Seite; ohne
Sperre wurde ein randlos-Bild im 596px schmalen Editor 1360px breit
(gemessen). Editor und Dashboard-Vorschau bleiben jetzt beim Lesemass.

Der Anriss trug sein Mass zuerst selbst und stand 27px zu weit links: `ch`
bemisst sich an der eigenen Schriftgroesse, und der Anriss laeuft in
text-xl — 65ch sind dort 649px statt 596px. Das Mass steht jetzt in einem
Rahmen in Grundschriftgroesse darum.

Gemessen in Chromium und WebKit bei 1440, 1024, 768 und 390px: schmal 45%,
halb 60%, breit 156%, randlos 228% des Masses; randlos bei 1024px korrekt
auf 944px begrenzt; Floats greifen bei halb+links/rechts, breit und randlos
schalten sie ab; :has() hebt die Breite auf eine <figure> mit Bildnachweis;
unterhalb 48rem faellt alles auf volle Breite; Autorenzeile, Anriss,
Artikelkoerper und Zurueck-Link fluchten auf 596px; kein waagerechter
Ueberlauf in keiner Kombination.
…chten

Eigene TipTap-Bildnotiz: Breite und Ausrichtung als Attribute, die als
Klassen ins Markup gehen. Am Bildrand sitzen Ziehgriffe, die beim Ziehen auf
die naechste Stufe einrasten (schmal, halb, Satzbreite, breit, randlos) und
die aktuelle Stufe anzeigen. Freies Skalieren waere nicht speicherbar: Der
HTML-Filter verwirft style-Attribute, nur class, width und height ueberleben.

Unterwegs kam heraus, dass der erste Ansatz die Speicherung gar nicht
erreicht haette. Der Editor speichert Markdown — getHTML, dann turndown,
dann onChange. Gemessen: Nach dem Ziehen auf „halb" stand in der Datenbank
nur `![JuPo Plakat](/api/uploads/…)`, die Einstellung war weg, weil Markdown
keine Bildbreiten kennt. Deshalb jetzt eine Turndown-Regel, die Bilder MIT
Klasse als rohes <img> schreibt; Bilder ohne Klasse bleiben Markdown, damit
sich am Bestand nichts aendert. Dieselbe Loesung nutzt die Datei laengst fuer
Unterstreichung, Durchstreichung und Tabellen.

mergeAttributes kommt aus @tiptap/react, nicht aus @tiptap/core: Der Kern ist
keine direkte Abhaengigkeit und unter pnpm nicht aufloesbar; @tiptap/react
reicht ihn vollstaendig durch.

Gemessen im laufenden Editor: Einfuegen, Knotenansicht mit zwei Ziehgriffen
und Stufenanzeige, Ziehen um 200px rastet auf „breit" ein und bleibt nach dem
Loslassen erhalten. Im Editor bleibt die Bildbreite dabei beim Lesemass —
richtig so, der Ausbruch haengt an --seite und gilt nur der oeffentlichen
Seite.

Rundlauf ueber einen Wegwerf-Beitrag: gespeichert wird
`<img src="…" alt="…" class="bild-halb">`, ohne die Umrandungsklassen der
Bearbeitungsansicht, und die Klasse uebersteht marked und den Filter. Kein
Rueckstand in der Datenbank.
Vorlage ist das Blechblatt selbst (Ausgabe 2025/01, Seiten 2-9): Kein
Artikel dort beginnt mit einem Bild ueber die volle Breite. Ueberschrift,
sofort Text, das Aufmacherbild etwa halbseitig daneben, Bildnachweis als
kleine Zeile darunter. Breite Bilder kommen vor, aber am Fuss der Seite,
nicht davor.

Gemessen vorher auf der Beitragsseite: Der Titelblock war 885px hoch, der
erste Absatz begann bei y=1383 — bei 1440x900 mussten 483px gescrollt
werden, bevor ein einziges Wort zu sehen war, bei 1440x1080 noch 303px.
Nachher steht der Text bei y=497 bzw. y=536, also sofort sichtbar.

Das Bild sitzt jetzt als umflossene Abbildung in der Textspalte: 60% der
Spalte, rechts, mit dem Nachweis als figcaption. Ein Float umfliesst nur
Text im selben Blockfluss, deshalb tragen Bild und Artikelkoerper eine
gemeinsame Huelle, und der Koerper gibt sein Lesemass dort ab
(--in-spalte). Editorflaeche und Dashboard-Vorschau haben keine solche
Huelle und behalten ihres unveraendert. Die Huelle braucht ein eigenes
clear, weil der Float ein Geschwister ist — das clear am Artikelende
greift dafuer nicht.

Auf dem Handy steht das Bild ueber dem Text statt daneben; dort kostete 3:2
zu viel Hoehe und schob den ersten Absatz bei einem Beitrag 20px unter die
Kante — derselbe Fehler, nur klein. Unterhalb sm jetzt 2:1, damit bleibt das
Bild und der Text sichtbar.

MediaCredit trug noch text-gray-500/dark:text-gray-400; da es hier zur
Bildunterschrift wird, jetzt Programmheft-Farben. Der Kontrast steigt in
beiden Modi (7,01:1 statt 4,83:1 hell, 7,68:1 statt 7,20:1 dunkel). Die
Variante fuer Auflagen auf Fotos bleibt unangetastet.

Gemessen in Chromium und WebKit bei 390x844, 1440x900 und 1440x1080: erster
Text ueberall ohne Scrollen sichtbar, Umfluss greift, Zurueck-Link bleibt
unter dem Bild, Ausbruchsbreiten unveraendert (928 / 1360), kein
waagerechter Ueberlauf, keine Seitenfehler.
Zwei Reste aus dem alten Design, die verschiedener Art sind.

Die Huelle um EditorContent trug text-gray-900 / dark:text-gray-100. Das ist
die Schreibflaeche selbst, also Oberflaeche und nicht Artikelinhalt — jetzt
Tinte. Gemessen: rgb(28,29,31) hell, rgb(236,235,232) dunkel.

Die TipTap-Attribute fuer Tabellenzellen und Kopfzellen schrieben
border-gray-300 / dark:border-gray-600 und bg-gray-100 / dark:bg-gray-800 in
jeden neuen Beitrag. Sichtbar wirkungslos, weil article-content.css sie
ueberstimmt — aber sie landen im gespeicherten HTML und bleiben dort als
tote Klassen liegen, jetzt erst recht, seit der Filter class durchlaesst.
Da das Stylesheet Tabellen ohnehin vollstaendig bestimmt, entfaellt die
Konfiguration ganz.

Nachgemessen an einer Tabelle ohne jede Klasse in der Schreibflaeche: Linien
1px rule bzw. night-rule, Kopfzeile 2px Tinte bzw. Nachtschrift darunter,
keine Fuellung, 12px Polster — in hell und dunkel. Das Entfernen kostet also
nichts.
Letzte zwei Stellen derselben Art wie die Tabellenzellen zuvor.

Link.configure schrieb `text-primary underline` in jeden gespeicherten
Beitrag. Sichtbar wirkungslos, weil article-content.css Links vollstaendig
bestimmt — aber es war ausgerechnet Orange als Schriftfarbe, also 1,99:1,
das dort dauerhaft abgelegt wurde. Table.configure schrieb
`border-collapse w-full my-4`, was das Stylesheet ebenfalls schon setzt.
`openOnClick` und `resizable` bleiben: Das ist Verhalten, keine Gestaltung.

Belegt statt angenommen: Ein Link ohne Klasse im Artikelkoerper rendert
primary-ink auf Papier (5,26:1) und Druckorange im Nachtdruck (9,16:1),
unterstrichen, Gewicht 500 — also genau das Vorgesehene. Und ein
bestehender Beitrag im Editor geoeffnet zeigt drei Links mit class=null,
die trotzdem korrekt eingefaerbt und unterstrichen sind.

Damit traegt die Erweiterungs-Konfiguration kein HTMLAttributes mehr;
Gestaltung steht ausschliesslich in article-content.css, fuer Schreibflaeche
und veroeffentlichten Beitrag gleichermassen.
Die Ausrichtung war vollstaendig vorhanden — als Attribut in der
Knotennotiz, als CSS mit Textumfluss, und die Turndown-Regel bewahrte sie
beim Speichern. Nur gab es keine Bedienung dafuer: Die Ziehgriffe aendern
allein die Groesse. Der Umfluss war damit eine Faehigkeit, die niemand
ausloesen konnte, und "in der Position verschieben" blieb eine halbe
Funktion.

Jetzt sitzen in der Auswahl-Ueberlagerung neben der Stufenanzeige drei
Knoepfe: Links, Mittig, Rechts. Sie tragen aria-pressed, damit der Zustand
nicht allein an der Farbe haengt, und 44px Hoehe wie im Dashboard-Kontrakt
gefordert — in einer neu gebauten Komponente will ich nicht unterschreiten,
was ich anderswo eingefordert habe.

Das preventDefault auf mousedown ist notwendig, nicht dekorativ: Ohne es
nimmt der Klick dem Editor den Fokus, ProseMirror hebt die Knotenauswahl
auf, und die Leiste verschwindet, bevor onClick greift.

Gemessen im laufenden Editor: Nach der Anwahl stehen die drei Knoepfe mit
"Mittig" auf aria-pressed=true; Ziehen setzt bild-halb und laesst die
Ausrichtung unberuehrt; ein Klick auf "Rechts" ergibt
class="bild-halb bild-rechts", data-ausrichtung=rechts und korrekt
umspringende aria-pressed-Werte, wobei die Knotenauswahl erhalten bleibt.

Rundlauf ueber einen Wegwerf-Beitrag: gespeichert wird
`<img … class="bild-halb bild-rechts">` — beide Klassen gemeinsam, ohne die
Umrandungsklassen der Bearbeitungsansicht, und sie ueberstehen marked und
den Filter. Bisher war nur die Groesse belegt. Kein Rueckstand.
…ernt

Sechs Bausteine trugen noch alte Tokens und schlugen damit auf laengst
umgestellte Seiten durch:

- loading-spinner: Grauschrift und oranger Ring -> Tinte. Haengt an vier
  oeffentlichen Seiten (Materialien, Blechblatt, Bezirke, Chor finden).
- image-with-fallback: bg-gray-800/dark:bg-gray-100 -> Tinte bzw.
  night-raised, wie der bereits umgestellte CoverFallback. Das Feld kehrte
  sich vorher um und brauchte deshalb zwei Logos; auf durchgehend dunklem
  Grund genuegt das helle.
- password-strength-meter: leere Spur und Hinweistext waren Grau. Die Skala
  rot/bernstein/limette/gruen bleibt — sie IST die Aussage der Anzeige.
- rich-text-editor-lazy: Ladezustand mit rounded-lg und Graustufen.
- dashboard-overflow-menu: text-gray-400 an den Zeichen.
- ensemble-map: alte Dunkel-Token, Graustufen und rounded-lg auf einer
  oeffentlichen Seite.

Dazu die Aufklappliste im Kursformular, die nicht nur zwei Dunkel-Token
trug, sondern rounded-lg, bg-white, shadow-lg, border-gray-200,
text-gray-800 und hover:bg-gray-100 — also komplett altes Design.

Geloescht, weil nachweislich ohne jeden Aufrufer (namensbasiert geprueft,
nicht ueber Pfade): ui/alert.tsx samt Sammelexport, posts/post-card.tsx,
general/user-menu.tsx. Mit post-card faellt der letzte Aufrufer von
getDistrictTextColor weg; die Funktion und das nie benutzte
getDistrictColorLight sind damit ebenfalls raus. Die Begruendung bleibt als
Kommentar stehen, damit niemand so etwas neu baut: Weder Schwarz noch Weiss
deckt die dreizehn Bezirksfarben ab.

Gemessen: Ensemblekarte auf drei Seiten mit Rundung 0px, Rahmen und Leiste
auf rule bzw. rule/25, Link auf dark; Passwortmesser mit leerer Spur auf
rule statt Grau; axe auf /register und /materialien je 0 Verstoesse in
Chromium und WebKit, keine Seitenfehler. tsc ueber den ganzen Baum sauber —
der Beleg, dass die drei geloeschten Dateien wirklich niemand brauchte.
Abschnittskoepfe (Vorstand, Posaunenrat, Materialien) klebten 21px unter
dem Kolumnentitel, ihre erste Zeile war verdeckt. Ensemble-Randspalte und
Bezirks-Infospalte nutzten ein festes top-24 und rutschten zusaetzlich
unter die Navigation.

Gemeinsame Klasse sticky-below-nav rechnet Navigation, Banner und die
Hoehe des Kolumnentitels ein, die PublicPage als Variable setzt. Der
Kolumnentitel hat dafuer eine feste Hoehe.
Der erste Abschnitt hatte als einzige Inhaltsseite kein oberes Polster:
85px zwischen Titel und erster Ueberschrift statt 181px wie auf
/mitmachen, /materialien und /kontakt.
Bei rechtsstehendem Kopf endete der Text linksbuendig mitten in der
Spalte, 242px vor dem Bild. Text, Termine und Hinweis stehen jetzt in
einem gemeinsamen Block von 65 Zeichen, der bei rechtsstehendem Kopf an
das Bild heranrueckt.
In Safari standen alle Auswahlchoere links: col-span-4 setzt als
grid-column-Kurzform auch den Start zurueck, ob col-start-9 danach noch
greift, hing an der Reihenfolge im Stylesheet. Kopf und Inhalt im Split
sowie die Randspalte des Newsletters setzen Start und Spanne jetzt in
einer Klasse.
Termin- und Kursansicht trugen als letzte Stellen noch `lg:top-24`. Die
feste Zahl ergibt 96px, die Navigation endet aber bei 120px: Auf
/termine/event/... lief die Randspalte gemessen 24px unter dem Streifen
hindurch, in Chromium wie WebKit und auf allen Breiten ab 1024px.

`sticky-below-nav` rechnet Banner- und Navigationshoehe zur Laufzeit
aus, wie schon auf der Ensemble- und der Bezirksseite. Nachgemessen
liegt die Spalte jetzt bei 189px mit 69px Luft.

Die Kursansicht klebt mit den heutigen Daten nie, weil ihre Randspalte
so hoch ist wie die Spalte daneben. Die Zahl war dort aber dieselbe
Falle, sobald ein Kurs laengeren Text bekommt.
Holt den Anmeldungs-Stack (#314, #316, #317) in die Neugestaltung.
Konflikte in sechs Dateien, jeweils mit der Programmheft-Fassung als
Grundlage:
- Anmeldeformular: Sperre bei ungeklaerter Platzwahl uebernommen, Knopf
  bleibt in Tinte.
- Schritt 3: pauschaler Wartelisten-Hinweis entfaellt, wie im Stack; den
  genauen Hinweis gibt jetzt seat-shortage-notice.
- Meine Anmeldungen und Anmeldungsdetail: Etiketten "Aufgeteilt" und
  "Nachruecken moeglich", Nachrueck-Angebot und Hinweis auf den anderen
  Teil einer aufgeteilten Anmeldung neu im Programmheft gesetzt.
- Dashboard-Teilnehmende: Verweise auf weitere Teile und das
  Nachrueck-Angebot als Etiketten.
@nichtLehdev
nichtLehdev changed the base branch from main to release/1.0.0 September 17, 2026 17:12
Die mit dem Stack gekommenen Bausteine standen noch im alten Stil:
Hinweis bei zu wenig Plaetzen, Wahl zwischen ganzer Warteliste und
Aufteilen samt Teilnehmerauswahl, und das Nachrueck-Angebot auf der
Anmeldungsseite. Jetzt mit Note, Tag, Haarlinien und Tintenrahmen fuer
die gewaehlte Karte; Texte und Verhalten unveraendert.

Die Radio-Knoepfe sind selbst gezeichnet: WebKit zeigte den nativen Knopf
im Nachtdruck als volle weisse Scheibe, die wie gewaehlt aussah. Der Link
auf den weiteren Teil einer Anmeldung im Dashboard behaelt die
Etikettenhoehe, trifft aber auf 44px.
@nichtLehdev
nichtLehdev merged commit 54d2b4c into release/1.0.0 Sep 17, 2026
@nichtLehdev nichtLehdev mentioned this pull request Sep 17, 2026
@nichtLehdev
nichtLehdev deleted the feat/programmheft-grundlagen branch September 18, 2026 11:57
nichtLehdev added a commit that referenced this pull request Sep 18, 2026
Programmheft: visuelle Neugestaltung der gesamten Plattform
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant