@import 'tailwindcss'; @import './fonts.css'; /* --------------------------------------------------------------------------- Design-Tokens. Fachliche Grundlage: docs/10-design-system.md. Bezugspunkt ist eine Registratur im Nachtdienst: lackierter Stahl, Aktenpapier, Stempelfarbe. Die einzige Farbe kommt von der Klassifizierung. --------------------------------------------------------------------------- */ @theme { /* Grundpalette, dunkel ist der Auslieferungszustand */ --color-ink-900: #15181a; /* Grundflaeche, kuehles Anthrazit, leicht gruenstichig */ --color-ink-800: #1d2124; /* Panel */ --color-ink-700: #262b2f; /* erhoehte Flaeche, Eingabefelder */ --color-ink-500: #3a4147; /* Linien und Rahmen */ --color-paper: #e4e7e4; /* Primaertext, papierfarbenes Off-White */ --color-paper-dim: #939c99; /* Sekundaertext */ --color-steel: #7a97a6; /* interaktiv: Links, Fokus, aktive Zustaende */ /* Klassifizierungsfarben. Das einzige Buntsystem, nirgends sonst benutzt. */ --color-stamp-0: #8a918d; /* Offen, Grau */ --color-stamp-1: #6d8a9c; /* Intern, Stahlblau */ --color-stamp-2: #4e8f72; /* Vertraulich, Gruenspan */ --color-stamp-3: #b08637; /* Geheim, Messing */ --color-stamp-4: #b4543c; /* Streng geheim, Oxidrot */ --color-stamp-5: #7e5e96; /* Leitungssache, Tiefviolett */ /* Semantische Ebene. Das helle Thema tauscht nur diese Werte. */ --color-surface: var(--color-ink-900); --color-panel: var(--color-ink-800); --color-raised: var(--color-ink-700); --color-line: var(--color-ink-500); --color-text: var(--color-paper); --color-text-dim: var(--color-paper-dim); --color-accent: var(--color-steel); /* Schrift. IBM Plex, selbst gehostet, drei Rollen aus einer Familie. */ --font-sans: 'IBM Plex Sans', ui-sans-serif, system-ui, sans-serif; --font-condensed: 'IBM Plex Sans Condensed', 'IBM Plex Sans', ui-sans-serif, sans-serif; --font-mono: 'IBM Plex Mono', ui-monospace, monospace; /* Skala 12 / 13 / 15 / 18 / 24 px */ --text-2xs: 0.75rem; /* 12 */ --text-xs: 0.8125rem; /* 13 */ --text-sm: 0.9375rem; /* 15 */ --text-base: 0.9375rem; /* 15, Fliesstext */ --text-lg: 1.125rem; /* 18 */ --text-xl: 1.5rem; /* 24 */ --text-2xs--line-height: 1.4; --text-xs--line-height: 1.45; --text-sm--line-height: 1.6; --text-base--line-height: 1.6; --text-lg--line-height: 1.45; --text-xl--line-height: 1.3; /* Struktur: Flaechen bleiben eckig, Bedienelemente sind rund. Eine Akte ist ein Blatt und hat scharfe Kanten. Was man druecken oder ausfuellen kann, ist rund — daran erkennt man es, bevor man es beruehrt. */ --radius-none: 0px; --radius-sm: 2px; --radius-DEFAULT: 2px; --radius-md: 2px; --radius-lg: 2px; --radius-xl: 2px; /* Eingabefelder: weich gerundet. Ein mehrzeiliges Feld kann keine Pille sein. */ --radius-field: 10px; /* Knoepfe, Suchbalken, Marken, Profilbild: voll gerundet. */ --radius-control: 999px; /* Keine Schatten ausser dem Fokusring. */ --shadow-sm: none; --shadow-DEFAULT: none; --shadow-md: none; --shadow-lg: none; /* Zeilenlaenge hoechstens 72 Zeichen */ --container-measure: 72ch; /* Die 3 px breite Klassifizierungskante links am Objekt */ --spacing-edge: 3px; } /* Helles Thema. * * Es tauscht **nur die semantische Ebene**, nie die Grundpalette und nie die * Klassifizierungsfarben: eine Stufe muss in beiden Themen dieselbe Farbe * haben, sonst lernt man sie zweimal. Siehe docs/10-design-system.md. * * Kein warmes Cremeweiss, sondern ein leicht kühles Papierweiss — dieselbe * Anmutung wie das dunkle Thema, nur andersherum. Die Kontraste sind auf * mindestens 4,5:1 für Fliesstext ausgelegt. */ [data-theme='light'] { --color-surface: #eceeea; --color-panel: #e2e5e1; --color-raised: #f7f8f5; --color-line: #b0b6ae; --color-text: #1b1f21; --color-text-dim: #4d5551; --color-accent: #35586a; } /* Die Klassifizierungsfarben sind für hellen Grund eine Spur dunkler, damit Stempel und Kante lesbar bleiben. Der Farbton bleibt derselbe. */ [data-theme='light'] { --color-stamp-0: #6b716d; --color-stamp-1: #4a6577; --color-stamp-2: #34694f; --color-stamp-3: #856024; --color-stamp-4: #8d3c28; --color-stamp-5: #5c4270; } /* Ohne ausdrückliche Wahl folgt die Anwendung dem System. Der Auslieferungs- zustand bleibt dunkel: `prefers-color-scheme: light` muss gesetzt sein. */ @media (prefers-color-scheme: light) { :root:not([data-theme='dark']) { --color-surface: #eceeea; --color-panel: #e2e5e1; --color-raised: #f7f8f5; --color-line: #b0b6ae; --color-text: #1b1f21; --color-text-dim: #4d5551; --color-accent: #35586a; --color-stamp-0: #6b716d; --color-stamp-1: #4a6577; --color-stamp-2: #34694f; --color-stamp-3: #856024; --color-stamp-4: #8d3c28; --color-stamp-5: #5c4270; } } @layer base { *, *::before, *::after { border-color: var(--color-line); } html { color-scheme: dark; background-color: var(--color-surface); } [data-theme='light'] { color-scheme: light; } body { background-color: var(--color-surface); color: var(--color-text); font-family: var(--font-sans); font-size: var(--text-base); line-height: 1.6; -webkit-font-smoothing: antialiased; } /* Spaltenkoepfe, Stempel und Reiter tragen den schmalen Schnitt. Kein Versalsatz: der Stempelcharakter kommt aus Schnitt, Rahmen und Farbe. */ th, legend { font-family: var(--font-condensed); font-weight: 500; text-transform: none; } /* Aktenzeichen, Zeitstempel, Hashes und Dienstnummern in Mono. */ code, kbd, samp, time { font-family: var(--font-mono); } h1, h2, h3 { font-weight: 500; text-wrap: balance; } h1 { font-size: var(--text-xl); } h2 { font-size: var(--text-lg); } h3 { font-size: var(--text-base); } a { color: var(--color-accent); text-underline-offset: 2px; } /* Auch Kaestchen und Schalter gehoeren zur interaktiven Ebene. */ input[type='checkbox'], input[type='radio'] { accent-color: var(--color-accent); } /* Sichtbarer Fokusring in --steel, der einzige Schatten im System. */ :focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; border-radius: 2px; } ::selection { background-color: color-mix(in srgb, var(--color-accent) 40%, transparent); } } @layer components { /* --------------------------------------------------------------------- Bedienelemente. Zwei Regeln, beide aus dem Betrieb heraus: **Ein Knopf muss vor der Beruehrung als Knopf erkennbar sein.** Deshalb traegt jeder einen Rahmen und eine eigene Grundflaeche, auch der unscheinbarste. Ein Text, der sich erst beim Ueberfahren als Knopf entpuppt, wird nicht gefunden. **Und er muss beim Druecken zurueckmelden, dass er getroffen wurde.** Beim Druecken zieht sich der Knopf leicht zusammen und bekommt einen inneren Ring — er wirkt kurz dicker, ohne dass sich die Rahmenbreite wirklich aendert, sonst wuerde das Umfeld springen. Die Rueckmeldung setzt ohne Verzoegerung ein und klingt langsam ab: ein kurzer Klick bliebe sonst unsichtbar. Siehe docs/10-design-system.md. --------------------------------------------------------------------- */ .btn { display: inline-flex; align-items: center; justify-content: center; gap: 0.4rem; border: 1px solid var(--color-line); border-radius: var(--radius-control); background-color: var(--color-raised); color: var(--color-text); padding: 0.3rem 0.85rem; font-family: var(--font-condensed); font-size: var(--text-xs); line-height: 1.45; text-align: center; text-decoration: none; white-space: nowrap; cursor: pointer; /* Abklingen langsam, damit der Druck sichtbar bleibt. */ transition: background-color 240ms ease-out, border-color 240ms ease-out, color 240ms ease-out, transform 240ms ease-out, box-shadow 240ms ease-out; } .btn:hover:not(:disabled):not([aria-disabled='true']) { background-color: var(--color-panel); border-color: var(--color-accent); color: var(--color-accent); transition-duration: 90ms; } .btn:active:not(:disabled):not([aria-disabled='true']) { transform: scale(0.96); border-color: var(--color-accent); box-shadow: inset 0 0 0 1.5px var(--color-accent); /* Sofort einsetzen, damit auch ein schneller Klick sichtbar wird. */ transition-duration: 0ms; } .btn:disabled, .btn[aria-disabled='true'] { color: var(--color-text-dim); border-style: dashed; cursor: not-allowed; } /* Der Knopf, der die Sache erledigt. Einer je Formular, nie zwei. */ .btn-primary { color: var(--color-accent); border-color: var(--color-accent); } .btn-primary:hover:not(:disabled) { background-color: color-mix(in srgb, var(--color-accent) 14%, var(--color-raised)); } /* Fuer dichte Zeilen: Etikettenleisten, Werkzeuge an einem Eintrag. */ .btn-sm { padding: 0.15rem 0.6rem; font-size: var(--text-2xs); } /* Ein Knopf, dessen Zustand er selbst ist: Etikett an oder aus, Reaktion gesetzt oder nicht, Reiter offen oder nicht. */ .btn[aria-current='page'], .btn[aria-pressed='true'] { background-color: color-mix(in srgb, var(--color-accent) 18%, var(--color-raised)); border-color: var(--color-accent); color: var(--color-accent); } /* Waehrend eine Aktion laeuft. Kein Spinner, nur ein ruhiger Zustand: der Knopf bleibt stehen, wo er ist, und nimmt keine zweite Eingabe an. */ .btn[aria-busy='true'] { color: var(--color-text-dim); border-style: dashed; cursor: progress; } /* Eingabefelder. Weich gerundet statt voll: ein mehrzeiliges Feld als Pille waere unlesbar. */ .field { border: 1px solid var(--color-line); border-radius: var(--radius-field); background-color: var(--color-raised); color: var(--color-text); padding: 0.4rem 0.7rem; font-family: inherit; font-size: var(--text-sm); line-height: 1.5; transition: border-color 120ms ease-out, background-color 120ms ease-out; } .field:hover:not(:disabled) { border-color: var(--color-accent); } .field:disabled { color: var(--color-text-dim); cursor: not-allowed; } /* ------------------------------------------------------------------- Beschriftung und Bedienelement. Der Abstand dazwischen war kleiner als die Polsterung **innerhalb** des Feldes, und genau daran erkennt das Auge einen zu engen Satz: das Wort steht naeher am Rahmen als der Inhalt des Rahmens an demselben Rahmen, also wirkt es angeklebt. Die Regel daraus: **der Abstand nach aussen ist groesser als die Polsterung nach innen.** Deshalb stehen beide Werte hier und nicht an den Aufrufstellen. Sie waren dort ein Dutzend Mal von Hand gesetzt, mit drei verschiedenen Werten, und keiner davon reichte. Siehe docs/10-design-system.md. ------------------------------------------------------------------- */ .field-label { display: block; /* Mehr als die 0.4rem Polsterung des Feldes nach oben und unten. */ margin-block-end: 0.5rem; font-family: var(--font-condensed); font-size: var(--text-2xs); line-height: 1.4; color: var(--color-text-dim); } /* Beschriftung daneben statt darueber. Waagerecht braucht es mehr Luft als senkrecht: das Feld polstert innen 0.7rem, aussen sind es 0.85rem. */ .field-row { display: flex; flex-wrap: wrap; align-items: center; gap: 0.85rem; font-family: var(--font-condensed); font-size: var(--text-2xs); color: var(--color-text-dim); } /* Der Hinweis unter einem Feld gehoert naeher an das Feld als an das naechste. */ .field-hint { display: block; margin-block-start: 0.4rem; font-size: var(--text-2xs); line-height: 1.45; color: var(--color-text-dim); } /* Einzeilige Felder, die wie ein Knopf aussehen duerfen: Suchbalken, Schnellfilter. */ .field-pill { border-radius: var(--radius-control); padding-inline: 0.9rem; } select.field { padding-block: 0.35rem; } /* Das Profilbild oben rechts und ueberall sonst, wo eine Person auftaucht. */ .avatar { display: inline-flex; align-items: center; justify-content: center; flex: none; border: 1px solid var(--color-line); border-radius: var(--radius-control); background-color: var(--color-raised); color: var(--color-text-dim); font-family: var(--font-condensed); overflow: hidden; object-fit: cover; } /* Der eine mutige Moment: die Schwaerzung. Ein massiver Balken mit feiner Rahmenlinie, der beim ersten Erscheinen einmal von links nach rechts durchwischt. Sonst bewegt sich in der Oberflaeche nichts von selbst. */ .redaction { display: inline-block; position: relative; overflow: hidden; vertical-align: baseline; background-color: var(--color-ink-900); border: 1px solid var(--color-line); border-radius: 2px; color: transparent; user-select: none; } .redaction::after { content: ''; position: absolute; inset: 0; background-color: var(--color-ink-700); transform-origin: left center; animation: redaction-wipe 150ms ease-out 1 both; } @keyframes redaction-wipe { from { transform: scaleX(1); } to { transform: scaleX(0); } } /* Zeitachse des Verlaufs: durchgehende senkrechte Linie. Der Chat hat keine. Der Unterschied muss ohne Beschriftung erkennbar sein. */ .timeline { position: relative; padding-left: 1.5rem; } .timeline::before { content: ''; position: absolute; left: 0.4375rem; top: 0; bottom: 0; width: 1px; background-color: var(--color-line); } /* --------------------------------------------------------------------- Das Aktenblatt. Bewusst **ausserhalb** des Design-Systems der Oberflaeche: schwarz auf weiss, harte Rahmen, Massangaben in Millimetern. Es ist kein Bildschirm- element, sondern ein Formular, das gedruckt und weitergereicht wird und auf Papier genauso aussehen muss wie hier. Deshalb steht hier kein einziger Farb-Token der Anwendung. Nachgebaut nach dem Formular, das die Personalabteilung bislang von Hand ausgefuellt hat. Siehe docs/13-entscheidungen.md. --------------------------------------------------------------------- */ .sheet { width: 210mm; min-height: 297mm; box-sizing: border-box; margin: 0 auto; padding: 10mm; background: #ffffff; color: #000000; font-family: var(--font-sans, sans-serif); } .sheet-frame { border: 4px solid #000; min-height: calc(297mm - 20mm); display: flex; flex-direction: column; } .sheet-head { display: flex; justify-content: space-between; align-items: center; gap: 1rem; padding: 15px; border-bottom: 4px solid #000; } .sheet-head h1 { margin: 0 0 5px; font-size: 28px; font-weight: 900; letter-spacing: 1px; line-height: 1.1; text-transform: uppercase; color: #000; } .sheet-head h2 { margin: 0; font-size: 14px; font-weight: 900; letter-spacing: 2px; text-transform: uppercase; color: #000; } .sheet-mark { display: flex; flex-direction: column; align-items: flex-end; gap: 10px; } /* Rein dekorativ, deshalb aria-hidden im Markup. */ .sheet-barcode { width: 140px; height: 25px; background: repeating-linear-gradient( 90deg, #000, #000 2px, #fff 2px, #fff 5px, #000 5px, #000 8px, #fff 8px, #fff 10px ); } .sheet-classbar { display: flex; justify-content: space-between; align-items: center; gap: 1rem; padding: 10px 15px; border-bottom: 4px solid #000; color: #fff; font-size: 14px; font-weight: 900; text-transform: uppercase; } .sheet-grid { width: 100%; border-collapse: collapse; table-layout: fixed; } .sheet-grid td { border: 2px solid #000; padding: 10px; vertical-align: top; word-wrap: break-word; overflow-wrap: anywhere; } .sheet-label { font-size: 10px; font-weight: 900; text-transform: uppercase; margin-bottom: 4px; color: #000; } .sheet-value { font-size: 14px; font-weight: 700; min-height: 20px; color: #000; } .sheet-value-emphasis { color: #7f1d1d; } .sheet-photo { width: 120px; height: 160px; margin: 0 auto; border: 2px solid #000; background: #f0f0f0; display: flex; align-items: center; justify-content: center; text-align: center; font-size: 10px; font-weight: 700; color: #555; position: relative; overflow: hidden; } .sheet-photo img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; } .sheet-signature { border-bottom: 2px solid #000; min-height: 30px; display: flex; align-items: flex-end; font-family: 'Times New Roman', Times, serif; font-style: italic; font-size: 18px; color: #000; } .sheet-foot { margin-top: auto; border-top: 4px solid #000; padding: 10px 15px; display: flex; justify-content: space-between; gap: 1rem; font-family: var(--font-mono, monospace); font-size: 10px; font-weight: 700; text-transform: uppercase; color: #000; } /* Schmale Fenster: das Blatt behaelt seine Proportionen und wird als Ganzes verkleinert, statt umzubrechen. Ein umgebrochenes Formular waere ein anderes Formular. */ @media (max-width: 230mm) { .sheet-scroll { overflow-x: auto; } } /* --------------------------------------------------------------------- Die Druckansicht einer Akte. Schwarz auf weiss wie das Aktenblatt, aber im Aufbau eines Berichts: Deckblatt, Kopfdaten, Abschnitte, Verlauf, Anhangsverzeichnis. Das Wasserzeichen setzt der PDF-Dienst in Kopf- und Fusszeile jeder Seite. Ein Export darf nie sauberer aussehen als die Akte ist: geänderte Einträge sind gekennzeichnet, zurückgezogene stehen als Grabstein, und gesperrte Felder erscheinen als Balken mit Vermerk statt zu fehlen. Siehe docs/08-suche-und-export.md. --------------------------------------------------------------------- */ .print-sheet { max-width: 180mm; margin: 0 auto; padding: 12mm 0; background: #fff; color: #000; font-size: 10pt; line-height: 1.45; /* Am Bildschirm auf schmalen Geraeten: die Tabellen darin scrollen in sich, statt die Seite breiter zu machen. Auf Papier greift das nicht. */ overflow-x: auto; } .print-meta { min-width: 0; } .print-sheet h1 { font-size: 18pt; font-weight: 900; margin: 0 0 2mm; color: #000; } .print-sheet h2 { font-size: 12pt; font-weight: 900; margin: 8mm 0 2mm; padding-bottom: 1mm; border-bottom: 1.5pt solid #000; color: #000; break-after: avoid; } .print-sheet h3 { font-size: 10pt; font-weight: 900; margin: 4mm 0 1mm; color: #000; break-after: avoid; } .print-cover { border: 3pt solid #000; padding: 8mm; margin-bottom: 8mm; } .print-stamp { display: inline-block; border: 2pt solid #000; padding: 1mm 3mm; font-weight: 900; letter-spacing: 1pt; text-transform: uppercase; } .print-meta { width: 100%; border-collapse: collapse; margin-top: 3mm; } .print-meta th, .print-meta td { border: 0.75pt solid #000; padding: 1.5mm 2mm; text-align: left; vertical-align: top; font-size: 9pt; } .print-meta th { width: 38mm; font-weight: 900; } /* Ein Eintrag soll nicht über den Seitenrand zerrissen werden. */ .print-entry { break-inside: avoid; padding: 2mm 0; border-bottom: 0.5pt dotted #666; } .print-label { font-size: 8pt; font-weight: 900; text-transform: uppercase; color: #000; } .print-note { font-size: 8pt; color: #444; } /* Die Schwärzung im Druck: ein voller Balken, kein Weglassen. */ .print-redacted { display: inline-block; min-width: 30mm; background: #000; color: #000; user-select: none; } } /* Drucken: nur das Blatt, ohne Rahmenwerk der Anwendung. Kopf- und Fusszeile mit dem Wasserzeichen setzt der PDF-Dienst, siehe docs/08. */ @media print { .no-print { display: none !important; } body { background: #fff !important; } .sheet { margin: 0; padding: 0; width: auto; } .sheet-frame { min-height: auto; } .sheet-classbar, .print-redacted, .print-stamp { /* Sonst drucken die Flächen weiss auf weiss. */ print-color-adjust: exact; -webkit-print-color-adjust: exact; } .print-sheet { max-width: none; padding: 0; } } @media (prefers-reduced-motion: reduce) { .redaction::after { animation: none; transform: scaleX(0); } *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } }