/* * Erscheinungsbild der Anwendung. * * Eine Datei, ein Farbsystem. Der Altbestand verteilte 4.000 Zeilen auf drei * Dateien mit einander widersprechenden Regeln zu denselben Selektoren und * ueber 200 !important-Angaben; Farben waren teils als CSS-Variable, teils * hartcodiert hinterlegt, sodass im dunklen Erscheinungsbild dunkler Text auf * dunklem Grund stand. * * Alle Farben kommen aus den Variablen unten. Helles und dunkles * Erscheinungsbild belegen dieselben Variablen unterschiedlich. */ /* * Alle Schriftgroessen und Geruestmasse relativ. * * Absolute px-Angaben uebergehen die Schriftgroesse, die der Anwender im * Betriebssystem eingestellt hat - wer sie dort vergroessert, sah hier keinen * Unterschied. Die kleinste Stufe liegt bei 0,75rem (12 Bildpunkte bei * Voreinstellung); darunter wird Fliesstext auch mit guten Augen muehsam. * Die Geruestmasse unten sind aus demselben Grund relativ: Waechst der Text, * ohne dass Kopfzeile, Statusleiste und Navigation mitwachsen, wird er dort * abgeschnitten. */ :root { color-scheme: light dark; --grund: #f4f6f9; --flaeche: #ffffff; --flaeche-erhoben: #ffffff; --flaeche-gedaempft: #eef1f6; --rand: #d3d9e3; /* * Begrenzung eines Bedienelements - Eingabefeld, Auswahlfeld, Schaltflaeche. * * #b3bccb erreichte auf der Flaeche darunter nur 1,91:1; wo ein Feld * aufhoert und der Hintergrund anfaengt, war damit kaum auszumachen, und in * den Matrizen verschwand das Raster der Eingabefelder ganz. Gefordert sind * 3:1 (WCAG 1.4.11). #7e8797 erreicht auf Weiss 3,62:1, auf der gedaempften * Flaeche #eef1f6 noch 3,20:1. --rand bleibt heller: Karten-, Tabellen- und * Trennlinien begrenzen nichts, was bedient wird. */ --rand-stark: #7e8797; --text: #1b2230; --text-gedaempft: #5b6577; --text-invers: #ffffff; --akzent: #1f5fa8; --akzent-hell: #e8f0fb; --akzent-text: #ffffff; --fehler: #b3261e; --fehler-flaeche: #fdecea; --warnung: #8a5a00; --warnung-flaeche: #fff4e0; --erfolg: #1f6b3a; --erfolg-flaeche: #e8f5ec; --hinweis: #33566e; --hinweis-flaeche: #eaf2f7; --signal-rot: #d13b2e; --signal-rotgelb: #e08b1f; --signal-gruen: #2e9e4f; --signal-gelb: #e8b923; --signal-dunkel: #5b6577; --radius: 6px; --radius-klein: 4px; --schatten: 0 1px 3px rgb(16 24 40 / 12%); --schatten-hoch: 0 12px 32px rgb(16 24 40 / 22%); --schrift: system-ui, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; --schrift-fest: 'Cascadia Mono', Consolas, 'SF Mono', 'Liberation Mono', monospace; --kopf-hoehe: 3.25rem; --leiste-hoehe: 1.875rem; --nav-breite: 14.5rem; } :root[data-erscheinungsbild='dunkel'] { --grund: #12161d; --flaeche: #1a1f28; --flaeche-erhoben: #222834; --flaeche-gedaempft: #171c24; --rand: #2f3743; /* #414b5a erreichte nur 1,87:1; #74808f erreicht 4,11:1 auf --flaeche und * 3,68:1 auf --flaeche-erhoben. */ --rand-stark: #74808f; --text: #e6eaf1; --text-gedaempft: #a3adbd; --text-invers: #12161d; --akzent: #5a9bea; --akzent-hell: #1b2a3d; --akzent-text: #0d1218; --fehler: #ff8a80; --fehler-flaeche: #3a1d1a; --warnung: #ffc65c; --warnung-flaeche: #3a2f14; --erfolg: #7ddc9c; --erfolg-flaeche: #14301f; --hinweis: #9dc8e8; --hinweis-flaeche: #17242e; --signal-rot: #e5584a; --signal-rotgelb: #f0a23a; --signal-gruen: #4cbc6e; --signal-gelb: #f4cf4a; --signal-dunkel: #6d7788; --schatten: 0 1px 3px rgb(0 0 0 / 45%); --schatten-hoch: 0 12px 32px rgb(0 0 0 / 60%); } * { box-sizing: border-box; } /* * `hidden` schlaegt jede Klassenregel. * * BEFUND AUS DEM BETRIEB (11.09.2026): In der Ansicht "Ausgabe" stand der * Warnbalken "Rechenweg abgewaehlt" dauerhaft da - auch bei gesetztem * Haekchen. Die Ansicht blendet ihn ueber das HTML-Merkmal `hidden` aus * (exportView.ts), und das wirkt allein ueber die Regel `[hidden] { * display: none }` des Browsers. Die hat die schwaechste Spezifitaet, die es * gibt, und `.hinweisbalken { display: flex }` weiter unten schlaegt sie. Der * Balken war damit nicht auszublenden. * * WARUM !important, obwohl der Kopf dieser Datei ueber 200 solcher Angaben im * Altbestand beklagt: Hier ist es kein Notbehelf gegen eine widersprechende * Regel, sondern die einzige Form, in der die Zusage "hidden blendet aus" * ueberhaupt gilt. Ohne sie muesste jede kuenftige Klasse mit eigenem * `display` daran denken - und die eine, die es vergisst, faellt erst im * Betrieb auf. Betroffen waren ausser dem Hinweisbalken keine der drei * uebrigen Stellen, die `hidden` setzen; das ist Zufall und keine Ordnung. */ [hidden] { display: none !important; } html, body { height: 100%; margin: 0; } body { font-family: var(--schrift); /* * Relative Schriftgroesse. * * 14px uebergingen die Schriftgroesse, die der Anwender im Betriebssystem * eingestellt hat - wer sie dort vergroessert, sah hier keinen Unterschied. * 0,875rem ist derselbe Wert bei Voreinstellung und waechst mit. */ font-size: 0.875rem; line-height: 1.5; color: var(--text); background: var(--grund); /* * Kein overflow:hidden mehr am Koerper: Bei starker Vergroesserung war der * untere Teil der Anwendung damit unerreichbar - er lag ausserhalb des * Fensters, und es liess sich nicht dorthin rollen. */ overflow: auto; } h1, h2, h3 { margin: 0; font-weight: 600; line-height: 1.3; } /* * Die Groesse muss der Rangfolge folgen: h1 > h2 > h3. * * Zuvor war h1 mit 15 px kleiner gesetzt als h2 mit 17 px. Sehend wirkte die * Hauptueberschrift dadurch untergeordnet, und wer sich beim Ueberfliegen an * der Groesse orientiert, wurde in die Irre gefuehrt. */ h1 { font-size: 1.25rem; } h2 { font-size: 1.0625rem; } h3 { font-size: 0.875rem; } p { margin: 0 0 8px; } a { color: var(--akzent); } /* * Fokusanzeige. * * Zweifarbig: Der helle Aussenring haelt den Rahmen auch auf dunklem Grund * sichtbar. Eine einfarbige Umrandung verschwindet je nach Untergrund - und wer * mit der Tastatur arbeitet, hat dann keine Ahnung mehr, wo er ist. */ :focus-visible { outline: 3px solid var(--akzent); outline-offset: 2px; box-shadow: 0 0 0 5px var(--flaeche); } /* * Nur fuer die Sprachausgabe. * * Der Text bleibt im Dokument und wird vorgelesen, nimmt aber keinen Platz ein. * Nicht ueber display:none oder visibility:hidden - beides nimmt den Text auch * der Sprachausgabe. Beim Fokussieren wird er sichtbar, damit Sprungmarken * ihren Zweck erfuellen. */ .nur-vorlesen { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; } .nur-vorlesen:focus, .nur-vorlesen:focus-within { position: static; width: auto; height: auto; margin: 0; overflow: visible; clip-path: none; white-space: normal; } /* Sprungmarke zum Inhalt - erster fokussierbarer Punkt des Dokuments. */ .sprungmarke:focus { position: fixed; top: 8px; left: 8px; z-index: 200; padding: 10px 16px; background: var(--flaeche); border: 2px solid var(--akzent); border-radius: var(--radius); font-weight: 600; } /* * Ruhigere Darstellung, wenn das Betriebssystem sie verlangt. * * Bewegung kann Uebelkeit und Schwindel ausloesen. Diese Regel nimmt Uebergaenge * und Animationen der Oberflaeche zurueck - mehr tut sie nicht, und mehr steht * auch sonst nirgends: Es gibt im Programm keine zweite Stelle, die * `prefers-reduced-motion` auswertet. * * WAS HIER FRUEHER STAND (Fassung 5.7.0): "Die Simulation laeuft weiter, aber * sie startet dann nicht von selbst; das steuert die Ansicht." Beides beschrieb * Code, den es nicht gibt. * Die Simulationsansicht wertet die Einstellung nicht aus - sie muss es auch * nicht: Ihre Bildschleife laeuft ueberhaupt nur nach einem Druck auf * "Starten" (src/ui/views/simulationView.ts, `start()`), und beim Verlassen der * Ansicht wird sie abgemeldet. Eine Bewegung, die niemals von selbst anfaengt, * braucht keine Ausnahme; ein Kommentar, der eine erfundene Steuerung * beschreibt, verleitet dagegen dazu, sie beim naechsten Umbau vorauszusetzen. */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } } /* Erhoehter Kontrast, wenn das Betriebssystem ihn verlangt. */ @media (prefers-contrast: more) { :root { --rand: #000; } /* * `!important` aus demselben Grund wie bei den Farbfeldern weiter unten: * `.knopf` und `input, select, textarea` setzen spaeter in dieser Datei die * Kurzschreibweise `border: 1px solid ...`, und die schreibt border-width * wieder auf 1 Bildpunkt zurueck. Bei gleicher Spezifitaet gewinnt die * spaetere Regel, eine Medienabfrage aendert daran nichts. Wer erhoehten * Kontrast eingestellt hat, sah also weiterhin genau denselben duennen Rand * und konnte in den Matrizen nicht ausmachen, wo ein Eingabefeld aufhoert * und die naechste Zelle anfaengt. */ .knopf, input, select, textarea { border-width: 2px !important; } } /* * Kontrastdesign des Betriebssystems. * * Windows ersetzt in diesem Modus alle Farben durch das Paar, das der Anwender * gewaehlt hat. Flaechen, die sich nur ueber ihre Hintergrundfarbe abheben - * Karten, Kennzahlen, Meldungen, Hinweise -, verschmelzen dann mit dem Grund: * Aus einem Pruefbericht wird eine einzige Textwand, in der nicht mehr zu sehen * ist, wo ein Befund aufhoert und der naechste anfaengt. Eine Kontur haelt sie * auseinander. */ @media (forced-colors: active) { .karte, .kennzahl, .meldung, .hinweisbalken, .befund, .fuehrung, .leer-hinweis, .assistent-erklaerung, .hilfe-kurz, .dialog { border: 1px solid CanvasText; } /* * Die Farbpunkte sind die einzige Auskunft darueber, welche Farbe eine * Signalgruppe im Signalzeitenplan traegt - werden sie durch die Systemfarbe * ersetzt, sehen alle gleich aus. Sie behalten deshalb ihre Farbe; eine * Kontur haelt sie auf jedem Grund sichtbar. * * `!important` ist hier keine Bequemlichkeit: `.farbpunkt` und * `.legende-farbe` setzen weiter unten in dieser Datei selbst eine * `border`-Kurzschreibweise. Eine Medienabfrage erhoeht die Spezifitaet * nicht, also gewinnt bei Gleichstand die spaetere Regel - die Kontur fiel * weg, und weil `forced-color-adjust: none` daneben die Systemfarben * fernhaelt, standen die Farbfelder ohne jede Begrenzung auf dem Grund: Ein * gelber Punkt auf gelbem Kontrastgrund ist schlicht nicht mehr da. Die * Regeln umzusortieren wuerde nur so lange helfen, bis jemand unten die * naechste ergaenzt. */ .farbpunkt, .legende-farbe, .leiste-punkt { border: 1px solid CanvasText !important; forced-color-adjust: none; } /* Der aktive Navigationspunkt zeigt sich sonst nur ueber die Flaeche. */ .nav-knopf[aria-current='page'] { border: 1px solid CanvasText; } /* * Der zweite, helle Ring der Fokusanzeige ist ein Schlagschatten - den * unterdrueckt das Kontrastdesign. Die Umrandung allein muss reichen. */ :focus-visible { outline: 3px solid CanvasText; } } /* --- Grundgeruest --------------------------------------------------------- */ .anwendung { display: grid; /* * Kopf- und Fusszeile duerfen wachsen. * * Zuvor waren beide auf eine feste Hoehe genagelt. Die neun Schaltflaechen der * Kopfleiste brauchen rund 900 Bildpunkte; wurde es enger, brach die Leiste um - * die Zeile blieb aber 52 Bildpunkte hoch, und Navigation und Inhalt malten * ihren Hintergrund ueber die zweite Knopfreihe. Die Knoepfe waren dann weder * zu sehen noch anzuklicken. * * Getroffen hat das genau die Vergroesserung auf 200 Prozent: Auf einem * Full-HD-Schirm sind das 960 Bildpunkte, und die rettende Medienabfrage * griff erst bei 900. */ grid-template-rows: minmax(var(--kopf-hoehe), auto) 1fr minmax(var(--leiste-hoehe), auto); grid-template-columns: var(--nav-breite) 1fr; grid-template-areas: 'kopf kopf' 'nav inhalt' 'leiste leiste'; /* * Mindestens die Fensterhoehe, aber nicht genau sie: Waechst die Kopfzeile * durch Umbruch, muss das Geruest mitwachsen duerfen, statt unten * abzuschneiden. */ min-height: 100vh; } .kopf { grid-area: kopf; display: flex; gap: 12px; align-items: center; padding: 0 12px; background: var(--flaeche); border-bottom: 1px solid var(--rand); } /* * Programmname in der Kopfzeile - Wortmarke, keine Ueberschrift. * * Als h1 gesetzt stand auf jeder Seite eine zweite, immer gleiche * Hauptueberschrift; die Ueberschriftengliederung war damit fuer eine * Sprachausgabe unbrauchbar. Die Marke behaelt ihr bisheriges Aussehen. */ .kopf-marke { flex-shrink: 0; font-size: 0.9375rem; font-weight: 600; } .kopf-titel { display: flex; flex-direction: column; min-width: 0; line-height: 1.15; } .kopf-titel strong { overflow: hidden; font-size: 0.875rem; text-overflow: ellipsis; white-space: nowrap; } .kopf-titel span { font-size: 0.75rem; color: var(--text-gedaempft); } .kopf-aktionen { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-left: auto; } .nav { grid-area: nav; padding: 8px; overflow-y: auto; background: var(--flaeche-gedaempft); border-right: 1px solid var(--rand); } .nav-gruppe { padding: 0 10px; margin: 14px 0 4px; font-size: 0.75rem; font-weight: 700; color: var(--text-gedaempft); text-transform: uppercase; letter-spacing: 0.06em; } .nav-gruppe:first-child { margin-top: 2px; } /* * Die Navigationsgruppen sind Listen. * * Zuvor hingen Ueberschrift und Schaltflaechen flach nebeneinander in der * `nav`: Es wurde nicht angesagt, wie viele Bereiche in "Erfassung" stecken, * und dass ein Knopf zu seiner Gruppe gehoert, war allein optisch zu sehen. * * Entfernt werden nur die Aufzaehlungszeichen - NICHT ueber `display: * contents`: Das nimmt die Listenrolle mit, und dann waere der Umbau * vergeblich gewesen. */ .nav-liste { padding: 0; margin: 0; list-style: none; } .nav-knopf { display: flex; gap: 8px; align-items: center; width: 100%; padding: 8px 10px; margin-bottom: 2px; font: inherit; color: var(--text); text-align: left; cursor: pointer; background: none; border: none; border-radius: var(--radius-klein); } /* Die Nummer macht die Bearbeitungsreihenfolge sichtbar. */ .nav-nummer { flex-shrink: 0; width: 1.25rem; height: 1.25rem; font-size: 0.75rem; font-weight: 600; line-height: 1.25rem; color: var(--text-gedaempft); text-align: center; background: var(--flaeche); border: 1px solid var(--rand); border-radius: 50%; } .nav-knopf[aria-current='page'] .nav-nummer { color: var(--akzent-text); background: var(--akzent); border-color: var(--akzent); } .nav-beschriftung { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .nav-knopf:hover { background: var(--flaeche); } .nav-knopf[aria-current='page'] { font-weight: 600; color: var(--akzent); background: var(--akzent-hell); } .nav-abzeichen { min-width: 1.25rem; padding: 0 5px; margin-left: auto; font-size: 0.75rem; font-weight: 600; color: var(--text-invers); text-align: center; border-radius: 9px; } /* * Formmerkmal neben der Farbe (Befund M11). * * Fehler und Warnung unterschieden sich am Abzeichen allein durch den * Hintergrund; die beiden Toene stehen zueinander bei 1,10:1 (hell) und * 1,47:1 (dunkel) und sind damit bei Farbfehlsichtigkeit und im * Schwarzweissdruck nicht zu trennen. Das Ausrufezeichen steht nur am * Fehlerabzeichen und macht den Unterschied ohne Farbe sichtbar; das Wort fuer * die Sprachausgabe setzt shell.ts als `nur-vorlesen` daneben. */ .nav-abzeichen-zeichen { margin-left: 2px; font-weight: 700; } .nav-abzeichen-fehler { color: var(--text-invers); background: var(--fehler); } .nav-abzeichen-warnung { color: var(--text-invers); background: var(--warnung); } .inhalt { grid-area: inhalt; padding: 16px; overflow: auto; } /* * Statusleiste. * * Die drei Abschnitte - Pruefstatus, Umlaufzeit, Projektstand - passen bei * 200 Prozent Vergroesserung nicht mehr nebeneinander. Ohne Umbruch liefen sie * waagerecht aus dem Fenster heraus; die Angaben rechts waren dann weder zu * sehen noch zu erreichen, und gerade dort steht, ob der Plan geprueft ist. * Jetzt bricht die Leiste um und die Geruestzeile waechst mit (siehe * grid-template-rows in .anwendung). * * Der Name --statusleiste haengt hier, damit sich die Kurzmeldungen an der * TATSAECHLICHEN Hoehe der Leiste ausrichten koennen - siehe .meldungen. */ .leiste { display: flex; flex-wrap: wrap; grid-area: leiste; gap: 4px 14px; align-items: center; min-height: var(--leiste-hoehe); padding: 4px 12px; font-size: 0.75rem; color: var(--text-gedaempft); background: var(--flaeche); border-top: 1px solid var(--rand); anchor-name: --statusleiste; } .leiste-abschnitt { display: flex; gap: 6px; align-items: center; white-space: nowrap; } .leiste-rechts { margin-left: auto; } .leiste-punkt { width: 8px; height: 8px; border-radius: 50%; } .leiste-punkt-ok { background: var(--erfolg); } .leiste-punkt-fehler { background: var(--fehler); } .leiste-punkt-warnung { background: var(--warnung); } /* --- Karten und Abschnitte ------------------------------------------------ */ /* * DIE BREITE WIRD DORT BEGRENZT, WO SIE STOERT - NICHT PAUSCHAL. * * Zuvor stand hier `max-width: 1400px; margin: 0 auto`. Der Grund war richtig: * Fliesstextzeilen von 200 Zeichen liest niemand gern. Die Stelle war es * nicht. Die Grenze traf die ganze Ansicht, also auch die Signalgruppentabelle * mit ihren sechzehn Spalten: Auf einem 1900 Bildpunkte breiten Fenster * standen links und rechts je rund 250 Bildpunkte leer, waehrend die Tabelle * in der Mitte waagerecht rollen musste. Ein Rollbalken, den man bei jeder * Zeile neu bedient, ist teurer als eine lange Zeile. * * Deshalb: Die Ansicht nimmt die Breite, die da ist. Begrenzt werden * Fliesstext (unten, 80 Zeichen) und einzelne Formularfelder (.karte .feld) - * beides Stellen, an denen Breite tatsaechlich schadet. Tabellen, Matrizen, * Zeitdiagramm und Lageplan bekommen den ganzen Platz. */ .ansicht { display: flex; flex-direction: column; gap: 16px; } /* * Zeilenlaenge des Fliesstexts. * * 80 Zeichen sind die uebliche Obergrenze fuer gut lesbaren Fliesstext. Ohne * diese Regel liefen die Erlaeuterungen im Pruefbericht und in den Vorgaben * auf einem breiten Schirm bis zu 200 Zeichen je Zeile - beim Zeilenwechsel * findet man dann den Anfang der naechsten Zeile nicht wieder. */ .ansicht p, .ansicht li { max-width: 80ch; } /* * Einzelne Formularfelder bekommen ein Mass, keine Bahn. * * Ein Textfeld ueber die volle Fensterbreite ist kein Gewinn: Man sieht * seinen Anfang und sein Ende nicht gleichzeitig, und der Bezug zur * Beschriftung darueber geht verloren. In der Vorgabenansicht war das * Fundstellenfeld ohne diese Regel 1567 Bildpunkte breit. * * Die Bemerkung ist ausgenommen - dort schreibt man ganze Saetze. */ .karte .feld { max-width: 52ch; } .karte .feld textarea { max-width: none; } .ansicht-kopf p { max-width: 78ch; margin: 4px 0 0; color: var(--text-gedaempft); } .karte { padding: 14px 16px; background: var(--flaeche); border: 1px solid var(--rand); border-radius: var(--radius); box-shadow: var(--schatten); } .abschnitt-kopf { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-bottom: 12px; } .abschnitt-aktionen { display: flex; flex-wrap: wrap; gap: 6px; margin-left: auto; } .raster { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px 16px; } .raster-schmal { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); } .leer-hinweis { padding: 24px; color: var(--text-gedaempft); text-align: center; background: var(--flaeche-gedaempft); border: 1px dashed var(--rand-stark); border-radius: var(--radius); } /* --- Formulare ------------------------------------------------------------ */ .feld { display: flex; flex-direction: column; gap: 4px; min-width: 0; } /* * Gruppe zusammengehoerender Bedienelemente. * * `fieldset` ist die einzige Auszeichnung, die einer Sprachausgabe sagt, dass * acht Kontrollkaestchen zusammen EINE Sache festlegen - ohne sie hoert man * acht unverbundene Schalter. Der Browser malt dazu aber Rahmen und * Innenabstand, die hier nicht ins Bild passen; die werden zurueckgenommen. * * `min-width: 0` ist kein Beiwerk: Ein `fieldset` schrumpft sonst nicht unter * die Breite seines Inhalts und sprengt jedes Raster, in dem es steht. */ .feldgruppe { min-width: 0; padding: 0; margin: 0; border: none; } /* * `.feld-name` traegt dieselbe Gestalt wie ein `label`, ist aber keins. * * Ein `label` gehoert zu einem Bedienelement. Wo nur ein errechneter Wert * steht - die ermittelte Umlaufzeit, die Freigabezeit im Plan -, gibt es * nichts zu bedienen; ein `label` ohne Ziel sagt einer Sprachausgabe * "Beschriftung" und laesst offen, wozu. Sichtbar bleibt es dieselbe Zeile. * * Die zweite Haelfte der Liste faengt den Fall ab, dass ein Hilfeknopf neben * der Beschriftung steht: feld() in src/ui/assistent.ts schiebt dafuer ein * `.feld-kopf` zwischen `.feld` und `label`, und ein Kindselektor allein greift * dann nicht mehr. Im gefuehrten Einstieg standen die Feldnamen dadurch in * Fliesstextgroesse und Normalstaerke - genauso gross wie der Wert darunter, * sodass Beschriftung und Eingabe nicht mehr auseinanderzuhalten waren. * * Es bleibt bei Kindselektoren. `.feld label` traefe auch das `label` aus * checkbox() in src/ui/dom.ts, das bewusst in `.feld-eingabe` neben dem * Kaestchen steht und die Beschriftung des Schalters traegt - klein und * gedaempft gesetzt waere sie die einzige Zeile, die den Schalter erklaert. */ .feld > label, .feld > .feld-name, .feld > .feld-kopf > label, .feld > .feld-kopf > .feld-name { display: block; font-size: 0.75rem; font-weight: 600; color: var(--text-gedaempft); } /* * Beschriftung und Hilfeknopf in einer Zeile. * * Der Hilfeknopf lag frueher IM label und wanderte damit in den vorgelesenen * Feldnamen ("Umlaufzeit Hilfe"). Herausgeloest braucht er eine Zeile, die * beides nebeneinander haelt, ohne dass der Knopf die Grundlinie verschiebt. */ .feld-kopf { display: flex; align-items: center; gap: 6px; } .feld-eingabe { display: flex; gap: 6px; align-items: center; } .feld-einheit { font-size: 0.75rem; color: var(--text-gedaempft); white-space: nowrap; } .feld-hinweis { margin: 0; font-size: 0.75rem; color: var(--text-gedaempft); } .feld-schalter .feld-eingabe { gap: 8px; } .feld-schalter label { font-size: 0.8125rem; font-weight: 400; color: var(--text); } input, select, textarea { width: 100%; min-width: 0; padding: 6px 8px; font: inherit; color: var(--text); background: var(--flaeche-erhoben); border: 1px solid var(--rand-stark); border-radius: var(--radius-klein); } /* * Platzhaltertext. * * Die Platzhalter tragen hier fachliche Werte - die Mindestfreigabezeit einer * Signalgruppe, den Regelwert eines Kennwerts, die Hinweise "berechnet" und * "verteilt". Wer sie nicht lesen kann, sieht nicht, welcher Wert gerade gilt. * Der Vorgabeton der Browser liegt je nach Browser zwischen 2,4:1 (Grauton * #a9a9a9 auf Weiss) und 3,6:1 (Textfarbe mit 54 Prozent Deckung) und damit * unter den geforderten 4,5:1. Die gedaempfte Textfarbe erreicht hell 5,88:1 * und dunkel 6,52:1 auf der Feldflaeche. Die Deckung wird ausdruecklich auf 1 * gesetzt, weil Firefox den Platzhalter sonst zusaetzlich abschwaecht. */ input::placeholder, textarea::placeholder { color: var(--text-gedaempft); opacity: 1; } /* * Kontrollkaestchen. * * 18 Bildpunkte Kaestchen plus 3 Bildpunkte Rand ringsum ergeben ein Ziel von * 24 Bildpunkten samt Abstand - das Mindestmass aus WCAG 2.5.8, ohne dass die * Kaestchen in der Vertraeglichkeits- und der Zwischenzeitenmatrix unfoermig * werden. Bei 16 Bildpunkten trifft sie mit dem Finger oder mit zittriger Hand * niemand zuverlaessig. */ input[type='checkbox'] { width: 18px; height: 18px; margin: 3px; accent-color: var(--akzent); } input[type='color'] { width: 42px; height: 30px; padding: 2px; } input:disabled, select:disabled, textarea:disabled { color: var(--text-gedaempft); background: var(--flaeche-gedaempft); } textarea { min-height: 72px; font-family: inherit; resize: vertical; } /* --- Schaltflaechen ------------------------------------------------------- */ .knopf { display: inline-flex; gap: 6px; align-items: center; padding: 6px 12px; font: inherit; font-size: 0.8125rem; color: var(--text); cursor: pointer; background: var(--flaeche-erhoben); border: 1px solid var(--rand-stark); border-radius: var(--radius-klein); } .knopf:hover:not(:disabled) { border-color: var(--akzent); } /* * Gesperrte Schaltflaeche. * * Zwei Abschwaechungen uebereinander - gedaempfte Textfarbe UND 55 Prozent * Deckung - druecken die Beschriftung auf 2,33:1 (#5b6577 zu 55 Prozent auf * Weiss ergibt #a5aab4). Lesbar ist das nicht mehr, und gerade bei einer * gesperrten Schaltflaeche muss zu erkennen sein, WELCHE Wahl gerade nicht * offensteht. Ein Mechanismus genuegt: Die gedaempfte Farbe traegt den * Kontrast - 5,19:1 hell, 7,55:1 dunkel auf der gedaempften Flaeche -, und die * gedaempfte Flaeche zeigt die Sperre auch dem, der Farben schlecht * unterscheidet. So halten es die Formularfelder oben bereits. * * Die Angaben schlagen auch bei .knopf-primaer und .knopf-gefahr durch: Klasse * plus Pseudoklasse wiegen schwerer als deren einzelne Klasse. */ .knopf:disabled { color: var(--text-gedaempft); cursor: not-allowed; background: var(--flaeche-gedaempft); border-color: var(--rand); } .knopf-primaer { font-weight: 600; color: var(--akzent-text); background: var(--akzent); border-color: var(--akzent); } .knopf-gefahr { color: var(--fehler); border-color: var(--fehler); } .knopf-schlicht { background: none; border-color: transparent; } .knopf-klein { padding: 3px 8px; font-size: 0.75rem; } /* --- Tabellen ------------------------------------------------------------- */ .tabelle-rahmen { /* * `position: relative` macht den Rahmen zum Bezugspunkt fuer alles, was in * ihm absolut positioniert ist - und damit zu dem, der es auch beschneidet. * * Ohne diese Zeile hing ein absolut positioniertes Kind ohne positionierten * Vorfahren am Dokument statt am Rahmen. Beschnitten wurde es dann von * niemandem: Die vorlesbaren Wertebereichshinweise in den Zellen der * Signalgruppentabelle standen rechnerisch bei Bildpunkt 1900 und zogen das * ganze Dokument auf 1938 Bildpunkte Breite. Die Folge war genau der Fehler, * den der rollende Rahmen verhindern soll: Nicht die Tabelle rollte * waagerecht, sondern die gesamte Seite - mitsamt Kopfzeile und Navigation. */ position: relative; overflow-x: auto; border: 1px solid var(--rand); border-radius: var(--radius); } table { width: 100%; border-collapse: collapse; } th, td { padding: 7px 10px; text-align: left; border-bottom: 1px solid var(--rand); } /* * Spaltenkoepfe duerfen umbrechen. * * Zuvor stand hier `white-space: nowrap`. Der Gedanke war richtig - er sollte * verhindern, dass ein Kopf zu "Zuf" gequetscht wird -, aber er zwang jeden * Kopf auf EINE Zeile, und ausgeschriebene Koepfe wie "Erhoehter Zeitbedarf" * oder "Mindestfreigabe" machten die Signalgruppentabelle dadurch 1916 * Bildpunkte breit. Sie rollte selbst auf einem 1900 Bildpunkte breiten * Fenster waagerecht. * * Umbrechen ist nicht Quetschen: Die Tabelle steht weiterhin auf * `width: max-content`, kein Feld wird schmaler als sein Inhalt. Der Kopf wird * zweizeilig - 74 statt 38 Bildpunkte hoch -, und die Tabelle passt mit 1602 * Bildpunkten in den Rahmen. Zwei Zeilen Kopf sind ein guenstiger Tausch gegen * einen Rollbalken, der bei jeder Zeile neu zu bedienen ist. */ th { position: sticky; top: 0; z-index: 1; font-size: 0.75rem; font-weight: 600; color: var(--text-gedaempft); background: var(--flaeche-gedaempft); vertical-align: bottom; } tbody tr:last-child td { border-bottom: none; } tbody tr:hover td { background: var(--flaeche-gedaempft); } td.zahl, th.zahl { font-variant-numeric: tabular-nums; text-align: right; } td.eng { width: 1%; white-space: nowrap; } /* * Tabellenbreite: Inhalt vor Fensterbreite. * * Zuvor galt table { width: 100% }. Bei sechzehn Spalten teilte der Browser die * Fensterbreite auf, quetschte jede Spalte und schnitt die Auswahlfelder ab - * aus "Kraftfahrzeuge" wurde "Kraftfahr", aus "Zufahrt Nord" wurde "Zufal". * Lesbar war das nicht, und ausgerechnet in einer Auswahlliste ist der * abgeschnittene Text die einzige Auskunft darueber, was gerade eingestellt ist. * * Jetzt bestimmt der Inhalt die Breite; ist die Tabelle breiter als das * Fenster, wird waagerecht gerollt. Fuer eine Datentabelle ist das der richtige * Weg - anders als bei Fliesstext gibt es hier nichts umzubrechen. */ .tabelle-rahmen table { width: auto; min-width: 100%; } /* * Fokusanzeige in der Tabelle. * * .tabelle-rahmen rollt waagerecht und ist damit ein Rollbereich - alles, was * ueber seinen Rand hinausragt, wird abgeschnitten, auch senkrecht. Die * Fokusanzeige ragt mit Umrandung, Abstand und hellem Aussenring bis zu zehn * Bildpunkte hinaus; in der letzten Zeile und am linken Rand blieb davon ein * angeschnittener Strich uebrig, und wer mit der Tastatur arbeitet, sah nicht * mehr, in welcher Zelle er steht. Nach innen gezeichnet bleibt der Ring * vollstaendig sichtbar. */ .tabelle-rahmen :focus-visible { outline-offset: -2px; /* * Der helle Aussenring wird durch einen Innenring ersetzt: Auf den * eingefaerbten Zellen der Zwischenzeiten- und der Vertraeglichkeitsmatrix * liegt der Rahmen sonst unmittelbar auf der Fuellfarbe. */ box-shadow: inset 0 0 0 2px var(--flaeche); } /* * Springt die Tastatur in eine Zelle, rollt der Browser sie an den oberen * Rand des Rollbereichs. Der klebende Spaltenkopf saesse dann darauf. */ .tabelle-rahmen th, .tabelle-rahmen td { scroll-margin-top: 44px; scroll-margin-left: 12px; scroll-margin-right: 12px; } table input, table select { padding: 4px 6px; font-size: 0.8125rem; } /* Auswahlfelder zeigen ihren Wert vollstaendig. */ table select { min-width: max-content; } table input[type='text'] { min-width: 14ch; } table input[type='number'] { min-width: 7ch; } /* Sichtbare Tabellenbeschriftung, wo sie gewuenscht ist. */ .tabelle-beschriftung { padding: 8px 10px; font-size: 0.8125rem; font-weight: 600; color: var(--text-gedaempft); text-align: left; } /* --- Zwischenzeitenmatrix ------------------------------------------------- */ .matrix { font-variant-numeric: tabular-nums; } .matrix th { text-align: center; } .matrix td { padding: 0; text-align: center; } .matrix-zelle { width: 100%; min-width: 54px; padding: 6px 4px; font: inherit; font-variant-numeric: tabular-nums; color: var(--text); text-align: center; cursor: pointer; background: none; border: none; } .matrix-zelle:hover { background: var(--akzent-hell); } .matrix-zelle-leer { color: var(--text-gedaempft); } /* * Die kritische Zelle traegt zusaetzlich eine Wellenlinie. * * Zuvor unterschied sich "Zwischenzeit 0 s bei bestehendem Konflikt" von einem * gueltigen Wert allein durch die rote Schrift - fuer Rotsehschwache und im * Schwarzweissdruck also gar nicht. Das Gegenstueck steht in * src/ui/views/conflictsView.ts: Die Zelle zeigt "0 !" statt einer blanken 0. */ .matrix-zelle-kritisch { font-weight: 600; color: var(--fehler); text-decoration: underline wavy; text-underline-offset: 3px; background: var(--fehler-flaeche); } .matrix-zelle-vorgegeben { font-style: italic; text-decoration: underline dotted; } /* * Traegt eine Zelle beide Zustaende, hat der kritische Vorrang: Dass die * Zwischenzeit 0 s betraegt, wiegt schwerer als die Herkunft des Werts. Ohne * diese Regel gewinnt die weiter unten stehende .matrix-zelle-vorgegeben mit * ihrer gepunkteten Linie, und die Warnung verschwindet still. */ .matrix-zelle-kritisch.matrix-zelle-vorgegeben { text-decoration: underline wavy; } .matrix-diagonale { background: var(--flaeche-gedaempft); } /* Vertraeglichkeitsmatrix - paarweise, nur obere Dreiecksmatrix bedienbar */ .vertraeglichkeit-zelle { min-width: 96px; font-size: 0.75rem; } .vertraeglichkeit-feindlich { font-weight: 600; color: var(--fehler); background: var(--fehler-flaeche); } .vertraeglichkeit-frei { color: var(--erfolg); background: var(--erfolg-flaeche); } .vertraeglichkeit-zelle:hover { outline: 2px solid var(--akzent); outline-offset: -2px; } /* --- Signalzeitenplan ----------------------------------------------------- */ .plan-rahmen { overflow-x: auto; background: var(--flaeche); border: 1px solid var(--rand); border-radius: var(--radius); } .plan-leinwand { display: block; } /* Zeit-Weg-Diagramm der Koordinierung - dieselbe Behandlung wie der Signalzeitenplan: eigener Rahmen, waagerecht rollbar, Leinwand als Block. */ .zeitweg-rahmen { overflow-x: auto; background: var(--flaeche); border: 1px solid var(--rand); border-radius: var(--radius); } .zeitweg { display: block; } .plan-legende { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 10px; font-size: 0.75rem; } .legende-eintrag { display: flex; gap: 6px; align-items: center; } .legende-farbe { width: 16px; height: 12px; border: 1px solid rgb(0 0 0 / 25%); border-radius: 2px; } /* * Strichprobe der Lageplan-Legende. * * Sie zeigt Farbe UND Strichmuster einer Verkehrsart. Das Muster ist im * Schwarzweissdruck und bei Rot-Gruen-Schwaeche die einzige Unterscheidung, * die bleibt - Kfz gegen Rad stehen als Farben nur 1,48:1 zueinander. Das * Muster selbst kommt als Farbverlauf aus der Ansicht, damit es nicht neben * der Zeichnung her gepflegt werden muss; hier stehen nur die Masse. * * `flex: 0 0 auto`, weil der Eintrag ein Flexbehaelter ist und die Probe sonst * auf null zusammenfaellt, sobald die Legende umbricht. */ .legende-strich { flex: 0 0 auto; width: 34px; height: 3px; border-radius: 1px; } /* * Der Hervorheben-Knopf steht in derselben Zelle wie das Namensfeld der * Fahrlinie. Ohne Abstand klebt er am Feldrand und sieht aus, als gehoere er * dazu - dann ist schwer zu treffen, was gemeint war. * * Bewusst ueber das Datenattribut und nicht ueber `.tabelle-rahmen td * .knopf-klein`: Letzteres traefe auch die Loeschknoepfe, die allein in ihrer * Zelle stehen. */ [data-hervorheben] { margin-left: 6px; } /* --- Pruefbericht --------------------------------------------------------- */ .befund { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; padding: 10px 12px; margin-bottom: 8px; border: 1px solid var(--rand); border-left-width: 4px; border-radius: var(--radius-klein); } .befund-fehler { background: var(--fehler-flaeche); border-left-color: var(--fehler); } .befund-warnung { background: var(--warnung-flaeche); border-left-color: var(--warnung); } .befund-hinweis { background: var(--hinweis-flaeche); border-left-color: var(--hinweis); } .befund-marke { padding: 1px 7px; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; border-radius: 9px; } .befund-fehler .befund-marke { color: var(--text-invers); background: var(--fehler); } .befund-warnung .befund-marke { color: var(--text-invers); background: var(--warnung); } .befund-hinweis .befund-marke { color: var(--text-invers); background: var(--hinweis); } .befund-titel { font-weight: 600; } .befund-koerper { grid-column: 2; } .befund-koerper p { margin: 2px 0; } .befund-quelle { font-size: 0.75rem; color: var(--text-gedaempft); } .befund-vorschlag { font-size: 0.8125rem; } .befund-vorschlag::before { content: '→ '; font-weight: 600; } /* --- Kennzahlen ----------------------------------------------------------- */ .kennzahlen { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; } .kennzahl { padding: 10px 12px; background: var(--flaeche-gedaempft); border: 1px solid var(--rand); border-radius: var(--radius-klein); } .kennzahl dt { font-size: 0.75rem; color: var(--text-gedaempft); } .kennzahl dd { margin: 2px 0 0; font-size: 1.1875rem; font-variant-numeric: tabular-nums; font-weight: 600; } .kennzahl-fussnote { font-size: 0.75rem; color: var(--text-gedaempft); } .stufe { display: inline-block; min-width: 22px; padding: 0 6px; font-weight: 700; color: var(--text-invers); text-align: center; border-radius: var(--radius-klein); } .stufe-a, .stufe-b { background: var(--erfolg); } .stufe-c, .stufe-d { background: var(--warnung); } .stufe-e, .stufe-f { background: var(--fehler); } /* --- Meldungen und Dialoge ------------------------------------------------ */ /* * Ablage der Kurzmeldungen, ueber der Statusleiste. * * Der Abstand nach unten war aus --leiste-hoehe gerechnet. Seit die Leiste * umbrechen darf, ist dieser Wert nur noch ihre Untergrenze: Bei zwei oder drei * Zeilen schoeben sich die Meldungen hinter die Leiste, und ausgerechnet eine * abgelehnte Eingabe waere dann nicht mehr zu lesen. * * Deshalb wird die tatsaechliche Oberkante der Leiste abgefragt. Wo das nicht * geht - aeltere Browser, oder wenn es gar keine Leiste gibt, etwa auf der * Startfehlerseite -, gilt weiter die alte Rechnung. Das max() faengt den Fall * ab, dass die Leiste beim Rollen unter den Fensterrand rutscht: Dann bleiben * die Meldungen am unteren Fensterrand stehen, statt mit hinauszuwandern. */ .meldungen { position: fixed; right: 16px; bottom: calc(var(--leiste-hoehe) + 16px); /* * UEBER dem Fensterhintergrund (.dialog-hintergrund, z-index 300). * * Der Meldungsstapel wird als einziger Teil des Hintergrunds nicht * stillgelegt, damit die Sprachausgabe seine Live-Bereiche weiter ansagt und * eine stehengebliebene Warnung schliessbar bleibt (legeHintergrundStill in * src/ui/feedback.ts). Bei z-index 200 traf beides nicht zu: Der halb * deckende Fensterhintergrund lag darueber, die Meldung war abgedunkelt, und * ein Klick auf ihren Schliessen-Knopf landete auf dem Hintergrund - der * schliesst das FENSTER. Wer im Zwischenzeitenfenster eine Zahl vertippt, * bekam so eine Warnung, die er nicht wegbekommt, ohne das Fenster zu * verlieren. * * Meldungen stehen unten rechts, Fenster mittig: Ueberdeckung ist die * Ausnahme, und im Zweifel gehoert die Warnung nach vorn. */ z-index: 400; display: flex; flex-direction: column; gap: 8px; max-width: 420px; /* * Warnungen und Fehler bleiben stehen, bis sie geschlossen werden (siehe * notify() in src/ui/feedback.ts). Mehrere koennen sich dadurch stapeln - * mehrfach abgelehnte Zahleneingaben erzeugen jede fuer sich eine Warnung. * Ohne Hoehenbegrenzung waechst der Stapel aus dem Bild heraus, und * ausgerechnet die aelteste Meldung, bei der es zuerst schiefging, waere * dann weder zu lesen noch zu schliessen. * * Wie bei `bottom` ist --leiste-hoehe nur die Ersatzrechnung fuer Browser * ohne Ankerbezug; das genaue Mass steht im @supports-Block darunter. */ max-height: calc(100vh - var(--leiste-hoehe) - 32px); overflow-y: auto; } /* * Die beiden Ansagebereiche im Meldungsstapel. * * Sie sind getrennt, weil eine Sprachausgabe Hinweise hoeflich und Warnungen * bestimmt ansagen soll - sichtbar bilden sie aber EINEN Stapel. Zuvor waren * es zwei eigene, gleich verortete Behaelter, die deckungsgleich uebereinander * lagen; seit Warnungen stehen bleiben, verdeckte der eine den anderen. */ .meldungen-gruppe { display: flex; flex-direction: column; gap: 8px; } /* Ein leerer Bereich darf keine Luecke im Stapel hinterlassen. */ .meldungen-gruppe:empty { display: none; } @supports (anchor-name: --statusleiste) { .meldungen { bottom: max(16px, calc(anchor(--statusleiste top, var(--leiste-hoehe)) + 16px)); /* * Die Hoehenbegrenzung muss derselben Kante folgen wie `bottom`, sonst * treten die beiden Angaben auseinander: `bottom` haengt an der * TATSAECHLICHEN Oberkante der Leiste, die Begrenzung rechnete aber mit * --leiste-hoehe - und das ist seit dem Umbruch nur noch die Untergrenze. * Bricht die Leiste auf drei Zeilen um, war der erlaubte Stapel um zwei * Zeilenhoehen zu hoch und wuchs oben wieder aus dem Fenster heraus, * genau an der aeltesten Meldung vorbei. anchor-size() liefert die * gemessene Hoehe; die Ersatzangabe greift, wo es gar keine Leiste gibt. */ max-height: calc(100vh - anchor-size(--statusleiste height, var(--leiste-hoehe)) - 32px); } } .meldung { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; background: var(--flaeche-erhoben); border: 1px solid var(--rand); border-left: 4px solid var(--hinweis); border-radius: var(--radius); box-shadow: var(--schatten-hoch); } .meldung-erfolg { border-left-color: var(--erfolg); } .meldung-warnung { border-left-color: var(--warnung); } .meldung-fehler { border-left-color: var(--fehler); } /* * Meldungsverlauf im Fenster "Meldungen dieser Sitzung". * * Dieselben Meldungsstuecke, nur untereinander in einer Liste statt schwebend * am Bildschirmrand: Der Schlagschatten gehoert zum schwebenden Balken und * waere hier nur Unruhe, und die Zeitangabe steht als eigene Zeile ueber dem * Text - im Verlauf ist sie das Einzige, was die Meldungen auseinanderhaelt. */ .meldungsverlauf { display: flex; flex-direction: column; gap: 8px; padding: 0; margin: 0; list-style: none; } .meldungsverlauf .meldung { flex-direction: column; gap: 2px; box-shadow: none; } .meldungsverlauf .meldung strong { font-size: 0.75rem; color: var(--text-gedaempft); } /* * 24 Bildpunkte im Quadrat sind das Mindestmass fuer ein Ziel, das getroffen * werden soll (WCAG 2.5.8); das Kreuz kam mit seinem Polster auf rund 20x21. * Es ist der einzige Weg, eine stehengebliebene Warnung wegzuraeumen. */ .meldung-schliessen { min-width: 24px; min-height: 24px; padding: 0 4px; margin-left: auto; font-size: 1.125rem; line-height: 1; justify-content: center; } .dialog-hintergrund { position: fixed; inset: 0; z-index: 300; display: flex; align-items: center; justify-content: center; padding: 20px; background: rgb(9 12 18 / 55%); } .dialog { width: 100%; max-width: 480px; max-height: calc(100vh - 40px); padding: 18px 20px; overflow: auto; background: var(--flaeche); border-radius: var(--radius); box-shadow: var(--schatten-hoch); } .dialog-gross { max-width: 900px; } .dialog-kopf { display: flex; gap: 10px; align-items: center; margin-bottom: 12px; } .dialog-schliessen { margin-left: auto; font-size: 1.25rem; line-height: 1; } .dialog-text { color: var(--text-gedaempft); white-space: pre-line; } .dialog-inhalt { display: flex; flex-direction: column; gap: 12px; } .dialog-aktionen { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; } /* --- Sonstiges ------------------------------------------------------------ */ .hinweisbalken { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; border: 1px solid var(--rand); border-left: 4px solid var(--hinweis); border-radius: var(--radius-klein); } .hinweisbalken-warnung { background: var(--warnung-flaeche); border-left-color: var(--warnung); } .hinweisbalken-fehler { background: var(--fehler-flaeche); border-left-color: var(--fehler); } .marke { display: inline-block; padding: 1px 7px; font-size: 0.75rem; font-weight: 600; border: 1px solid var(--rand-stark); border-radius: 9px; } /* Lageplan */ .lageplan-anordnung { display: grid; grid-template-columns: minmax(0, 3fr) minmax(300px, 1fr); gap: 14px; align-items: start; } @media (width <= 1100px) { .lageplan-anordnung { grid-template-columns: 1fr; } } .lageplan-rahmen { overflow: hidden; background: var(--flaeche); border: 1px solid var(--rand); border-radius: var(--radius); } .lageplan-leinwand { display: block; cursor: crosshair; } /* * Fokusanzeige der Zeichenflaeche. * * Die globale zweifarbige Anzeige traegt hier nicht: Ihr heller Aussenring * liegt ausserhalb des Inhaltskastens und wird von der `overflow: hidden` des * Rahmens weggeschnitten. Uebrig blieb eine einfarbige Linie in der * Akzentfarbe, nach innen versetzt - und die liegt ueber einem Luftbild auf * beliebigem Untergrund. WCAG 1.4.11 fordert 3:1 gegenueber der Umgebung; ueber * einem Orthophoto ist das mit einer einzigen Farbe nicht zuzusichern. * * Deshalb zwei Ringe INNERHALB der Flaeche, hell gegen dunkel. Welcher von * beiden gerade traegt, haengt vom Bild ab - einer davon traegt immer. Der * Schatten liegt innen (`inset`) und wird deshalb nicht abgeschnitten. */ .lageplan-leinwand:focus-visible { outline: 2px solid var(--akzent); outline-offset: -2px; box-shadow: inset 0 0 0 4px #ffffff, inset 0 0 0 6px #10151f; } .lageplan-seite { display: flex; flex-direction: column; gap: 12px; } /* * Haltlinien- und Fahrlinientabelle stehen unter der Zeichenflaeche. * * In der Seitenleiste hatten sie rund 300 Bildpunkte. Die Fahrlinientabelle * fuehrt aber acht Spalten, darunter zwei Auswahlfelder mit Namen wie * "Zufahrt Nord geradeaus" - davon blieb "Zuf" uebrig. Ueber die volle Breite * sind beide lesbar und nebeneinander vergleichbar. */ .lageplan-tabellen { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); gap: 12px; align-items: start; margin-top: 12px; } @media (width <= 1100px) { .lageplan-tabellen { grid-template-columns: 1fr; } } .lageplan-werkzeuge { display: flex; flex-wrap: wrap; gap: 6px; } /* * Zeilenzustaende schlagen die Ueberfahr-Einfaerbung. * * `tbody tr:hover td` hat drei Typen und eine Pseudoklasse, also (0,1,3); * `.lageplan-aktiv td` kam auf (0,1,1) und verlor. Sobald der Zeiger auf der * Zeile stand, verschwand damit genau die Auskunft, die sie traegt: WELCHE * Haltlinie gerade in der Zeichnung hervorgehoben ist und WELCHER Weg vom * gemessenen Wert abweicht. Wer mit der Maus die Liste entlangfaehrt, bekam * die Zeile unter dem Zeiger als einzige ohne Zustand zu sehen. * * Mit `tbody tr.…` stehen beide Regeln auf (0,1,3); die spaetere gewinnt, und * das sind diese hier. Die Hover-Einfaerbung bleibt fuer alle uebrigen Zeilen. */ tbody tr.lageplan-aktiv td { background: var(--akzent-hell); } tbody tr.lageplan-abweichung td { background: var(--warnung-flaeche); } /* Naechster Arbeitsschritt */ /* * Steht UNTER der Ansicht und richtet sich nach ihr - siehe .ansicht. * * Die Form ist die eines Abschlusses und nicht die eines Hinweises: eine * Trennlinie nach oben statt einer Farbkante links. Solange die Fuehrung oben * stand, teilte sie Bauform und - in der Fehlerstufe - auch die Farbe mit den * Hinweisbalken der Ansichten; derselbe Platz und dieselbe Farbe fuer zwei * verschiedene Dinge. Der Platz oben gehoert jetzt allein dem, was JETZT und * HIER gilt. */ .fuehrung-halter { margin: 24px 0 0; } .fuehrung { display: flex; gap: 14px; align-items: center; padding: 16px 2px 2px; border-top: 1px solid var(--rand); } /* * Die Dringlichkeit faerbt die Marke, nicht die Flaeche. * * Eine getoente Flaeche unter der Ansicht laese sich wieder mit einem * Hinweisbalken verwechseln - und ein fehlender Raeumweg gehoert ohnehin * zuerst in den Pruefbericht und an das Abzeichen seiner Ansicht, nicht in * einen Wegweiser. */ .fuehrung-fehler .fuehrung-marke { background: var(--warnung); } .fuehrung-fertig .fuehrung-marke { background: var(--erfolg); } .fuehrung-text { min-width: 0; } .fuehrung-text strong { display: flex; flex-wrap: wrap; gap: 8px; align-items: baseline; } .fuehrung-marke { padding: 1px 7px; font-size: 0.75rem; font-weight: 700; color: var(--text-invers); text-transform: uppercase; letter-spacing: 0.03em; background: var(--akzent); border-radius: 9px; } .fuehrung-text p { max-width: 92ch; margin: 3px 0 0; font-size: 0.8125rem; color: var(--text-gedaempft); } .fuehrung > .knopf { flex-shrink: 0; margin-left: auto; } /* * Die Marke "als Naechstes dran" in der Navigationsleiste. * * Ein Punkt in der Akzentfarbe, kein zweites Abzeichen: Das Abzeichen daneben * zaehlt Beanstandungen, und zwei zaehlende Zeichen an einem Eintrag waeren * nicht auseinanderzuhalten. Die Farbe allein traegt die Aussage nicht - der * ausgeschriebene Text "als Nächstes dran" steht fuer die Sprachausgabe * daneben, und die halbfette Beschriftung zeigt es auch ohne Farbe. */ .nav-naechster { flex-shrink: 0; margin-left: 4px; font-size: 0.7rem; line-height: 1; color: var(--akzent); } .nav-knopf-naechster .nav-beschriftung { font-weight: 600; } /* * Der Haken "erledigt". * * In der Erfolgsfarbe und deutlich zurueckhaltender als die Marke daneben: Er * ist die schwaechste der drei Aussagen am Eintrag - was erledigt ist, braucht * keine Aufmerksamkeit mehr. Farbe traegt ihn nicht allein; das Zeichen selbst * ist eines, und der ausgeschriebene Zusatz steht fuer die Sprachausgabe * daneben. */ .nav-fertig { flex-shrink: 0; margin-left: 4px; font-size: 0.75rem; line-height: 1; color: var(--erfolg); } /* Die beiden Verweise im "Über"-Fenster - nebeneinander, mit Abstand darum. */ .ueber-verweise { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 0; } .assistent-erklaerung { max-width: 80ch; padding: 10px 12px; color: var(--text); background: var(--flaeche-gedaempft); border-radius: var(--radius-klein); } /* Hilfe */ /* * Hilfeknopf. * * 24 Bildpunkte im Quadrat sind das Mindestmass fuer ein Ziel, das mit dem * Finger oder mit zittriger Hand getroffen werden soll (WCAG 2.5.8). Der Knopf * steht in Spaltenkoepfen dicht neben der Beschriftung und in Feldkoepfen dicht * neben dem Feldnamen - bei 18 Bildpunkten trifft ihn dort niemand * zuverlaessig, und wer danebengreift, oeffnet stattdessen die Sortierung oder * setzt den Fokus ins Feld. * * justify-content ist noetig, weil .hilfe-knopf von .knopf 'display: * inline-flex' erbt: ohne waagerechte Zentrierung klebt das Fragezeichen im * groesseren Kreis am linken Rand. */ .hilfe-knopf { width: 24px; height: 24px; padding: 0; margin-left: 5px; font-size: 0.75rem; font-weight: 700; line-height: 1; color: var(--akzent); vertical-align: middle; background: var(--akzent-hell); border: 1px solid transparent; border-radius: 50%; justify-content: center; } .hilfe-knopf:hover:not(:disabled) { color: var(--akzent-text); background: var(--akzent); border-color: var(--akzent); } th .hilfe-knopf { margin-left: 4px; } .hilfe-kurz { padding: 10px 12px; font-size: 0.9375rem; background: var(--akzent-hell); border-radius: var(--radius-klein); } .hilfe-abschnitt h3 { margin-bottom: 2px; font-size: 0.8125rem; color: var(--text-gedaempft); text-transform: uppercase; letter-spacing: 0.03em; } .hilfe-abschnitt p { max-width: 80ch; margin: 0; /* Absaetze und Aufzaehlungen aus dem Hilfetext sichtbar machen. */ white-space: pre-line; } .hilfe-warnung { padding: 8px 12px; background: var(--warnung-flaeche); border-left: 3px solid var(--warnung); border-radius: var(--radius-klein); } .hilfe-verweise { padding-top: 10px; border-top: 1px solid var(--rand); } .hilfe-verweis { color: var(--akzent); text-decoration: underline; } .glossar-liste { display: flex; flex-direction: column; gap: 2px; max-height: 52vh; overflow-y: auto; } .glossar-eintrag { padding: 8px 10px; border-bottom: 1px solid var(--rand); } .glossar-eintrag:hover { background: var(--flaeche-gedaempft); } .glossar-kopf { display: flex; gap: 8px; align-items: center; } .glossar-kopf .knopf { margin-left: auto; } .glossar-eintrag p { margin: 2px 0 0; font-size: 0.8125rem; color: var(--text-gedaempft); } /* Herkunft der Kennwerte */ .marke-gesichert { color: var(--erfolg); background: var(--erfolg-flaeche); border-color: var(--erfolg); } .marke-abzugleichen { color: var(--warnung); background: var(--warnung-flaeche); border-color: var(--warnung); } .marke-fachliteratur { color: var(--hinweis); background: var(--hinweis-flaeche); border-color: var(--hinweis); } .herkunft { padding: 8px 10px; margin-top: 10px; background: var(--flaeche-gedaempft); border: 1px solid var(--rand); border-radius: var(--radius-klein); } .herkunft > summary { cursor: pointer; list-style: revert; } .herkunft > summary::marker { color: var(--text-gedaempft); } .herkunft-inhalt { padding-top: 8px; margin-top: 8px; border-top: 1px solid var(--rand); } .herkunft-inhalt p { max-width: 90ch; } .farbpunkt { display: inline-block; width: 11px; height: 11px; margin-right: 6px; vertical-align: -1px; border: 1px solid rgb(0 0 0 / 25%); border-radius: 2px; } .tastenkuerzel { padding: 1px 5px; font-family: var(--schrift-fest); font-size: 0.75rem; background: var(--flaeche-gedaempft); border: 1px solid var(--rand-stark); border-radius: 3px; } /* Schmale Fenster: die Navigation rueckt ueber den Inhalt. */ @media (width <= 900px) { .anwendung { grid-template-columns: 1fr; grid-template-areas: 'kopf' 'nav' 'inhalt' 'leiste'; /* Auch im schmalen Fenster darf die Fusszeile umbrechen und mitwachsen. */ grid-template-rows: auto auto 1fr minmax(var(--leiste-hoehe), auto); } .nav { display: flex; gap: 4px; align-items: center; overflow-x: auto; border-right: none; border-bottom: 1px solid var(--rand); } /* * Auch die Gruppenlisten laufen quer. Ohne diese Regel stuenden die * Eintraege jeder Gruppe wieder untereinander, und die Navigationsleiste * waere im schmalen Fenster so hoch wie die ganze Anwendung. */ .nav-liste { display: flex; flex-shrink: 0; gap: 4px; } .nav-gruppe { flex-shrink: 0; margin: 0; white-space: nowrap; } .nav-knopf { width: auto; white-space: nowrap; } } @media print { .kopf, .nav, .leiste, .meldungen, .abschnitt-aktionen { display: none !important; } .anwendung { display: block; height: auto; } .inhalt { padding: 0; overflow: visible; } body { overflow: visible; background: #fff; } }