From 2ac7d3f64c408d5ef4c50afeeb37dfe16b3871f5 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 17:21:31 +0000 Subject: [PATCH] Kundenansicht neu P2: Aussehen der Ansichten Co-Authored-By: Claude Sonnet 5.5 --- public/neu/ansichten.css | 350 ++++++++++++++++++++++++- public/neu/oeffentlich/oeffentlich.css | 45 +++- test/knAnsichtStil.test.js | 29 ++ 3 files changed, 411 insertions(+), 13 deletions(-) create mode 100644 test/knAnsichtStil.test.js diff --git a/public/neu/ansichten.css b/public/neu/ansichten.css index d64179c..e3a7e6d 100644 --- a/public/neu/ansichten.css +++ b/public/neu/ansichten.css @@ -1,2 +1,348 @@ -/* Kundenansicht neu: Stile der Ansichten Website, Chat, Verträge und der Etagen Passwort, Gutschrift, Wechsel, Bestellen. - Besitzer: P2 (danach hängt P7 einen Abschnitt für die Bestellseiten an). Hier steht bis dahin nichts. */ +/* Kundenansicht neu: Aussehen der Ansichten Website, Chat, Verträge und der Etagen Passwort, Gutschrift, Wechsel, Bestellen. + Besitzer: P2 (danach hängt P7 einen Abschnitt für die Bestellseiten an). Werte nur über Tokens (tokens.css); Farbliterale stehen + ausschließlich in den --kn-Deklarationen direkt unten. Abschnitte durch Haarlinien, keine Kästen; Radien 4, 6 und 12 px. */ + +/* ------------------------------------------------------------------ Zusatz-Tokens (nur für diese Datei) --- */ +.kn { + --kn-spur: rgba(26,31,54,.2); + --kn-ablage-hell: rgba(255,255,255,.92); +} + +.kn .hidden { display: none !important; } + +/* ------------------------------------------------------------------ Gemeinsame Bausteine der Ansichten --- */ +.kn-hinweis { margin: 8px 0 0; font-size: 13px; line-height: 20px; color: var(--kn-text-2); } +.kn-notiz { margin: 8px 0 0; font-size: 13px; line-height: 20px; color: var(--kn-text-2); } +.kn-umbruch { white-space: pre-wrap; } +.kn-panel { margin: 0 0 24px; } +.kn-panel > h3, .kn-sektion h3, .kn-vertrag h3, .kn-wechsel h3, .kn-abo-karte h3 { margin: 0 0 8px; font-size: 16px; line-height: 24px; font-weight: 600; } +.kn-sektion { margin-top: 32px; } +.kn-sektion:first-child { margin-top: 0; } +.kn-aktionen { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 16px; } +.kn-preis { margin: 0 0 4px; font-size: 14px; color: var(--kn-text-2); font-variant-numeric: tabular-nums; } +.kn-preis strong { font-size: 20px; line-height: 28px; font-weight: 700; color: var(--kn-text); letter-spacing: -.01em; } +.kn-links { margin: 12px 0 0; font-size: 14px; } +.kn-gesperrt { opacity: .55; cursor: not-allowed; pointer-events: none; } +.kn-zeilenlink { margin-right: 12px; font-weight: 500; } +.kn-zeit { flex: none; font-variant-numeric: tabular-nums; color: var(--kn-text-2); white-space: nowrap; } +.kn-badge { + display: inline-grid; place-items: center; min-width: 18px; height: 18px; padding: 0 5px; border-radius: 50%; + background: var(--kn-accent); color: var(--kn-bg); font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums; +} + +/* Tabellen (Rechnungen, Dateien, Zusammenfassungen) */ +.kn-tabelle { overflow-x: auto; } +.kn-tabelle table, .kn-zusammenfassung { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; } +.kn-tabelle th { padding: 8px 12px 8px 0; border-bottom: 1px solid var(--kn-line); text-align: left; font-size: 12px; font-weight: 500; color: var(--kn-text-2); white-space: nowrap; } +.kn-tabelle td { padding: 12px 12px 12px 0; border-bottom: 1px solid var(--kn-line); vertical-align: top; } +.kn-tabelle tr:last-child td { border-bottom: 0; } +.kn-zusammenfassung td { padding: 10px 12px 10px 0; border-top: 1px solid var(--kn-line); vertical-align: top; overflow-wrap: anywhere; } +.kn-zusammenfassung tr:last-child td { border-bottom: 1px solid var(--kn-line); } +.kn-zusammenfassung td:first-child { width: 11rem; color: var(--kn-text-2); } +.kn-uebersicht { margin: 16px 0; } + +/* Fakten als Liste */ +.kn-fakten { display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; margin: 0 0 16px; } +.kn-fakten > div { display: contents; } +.kn-fakten dt { color: var(--kn-text-2); } +.kn-fakten dd { margin: 0; min-width: 0; overflow-wrap: anywhere; } + +/* Leistungen eines Abos */ +.kn-leistungen { list-style: none; margin: 12px 0; padding: 0; } +.kn-leistungen li { padding: 8px 0; border-top: 1px solid var(--kn-line); } +.kn-leistungen li:last-child { border-bottom: 1px solid var(--kn-line); } +.kn-leistungen small { display: block; color: var(--kn-text-2); font-size: 13px; } + +/* Angebotskasten über der Bestellung */ +.kn-angebot-kasten { margin: 0 0 24px; padding: 12px 16px; border-radius: var(--kn-radius); background: var(--kn-bg-subtle); } +.kn-angebot-kasten p { margin: 4px 0 0; } +.kn-angebot { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 8px; } +.kn-angebot-text { color: var(--kn-text-2); } + +/* Ein-/Ausklappen langer Listen */ +.kn-einklappbar:not(.is-open) [data-extra] { display: none; } +.kn-mehr { margin-top: 8px; } + +/* ------------------------------------------------------------------ Website --- */ +.kn-rechnung-offen { margin: 0 0 16px; padding: 8px 12px; border-radius: var(--kn-radius); background: var(--kn-orange-bg); color: var(--kn-orange-fg); font-weight: 500; } +.kn-rechnung-offen a { text-decoration-color: currentColor; } +.kn-install-platz:empty { display: none; } +.kn-install { display: flex; align-items: center; gap: 12px; margin: 0 0 16px; padding: 12px 0; border-top: 1px solid var(--kn-line); border-bottom: 1px solid var(--kn-line); } +.kn-install-text { flex: 1; min-width: 0; } +.kn-install-text strong, .kn-install-text span { display: block; } +.kn-install-text span { font-size: 13px; color: var(--kn-text-2); } +.kn-projekte { list-style: none; margin: 0; padding: 0; } +.kn-projekt { display: grid; grid-template-columns: minmax(0, 360px) minmax(0, 1fr); gap: 24px; padding: 24px 0; border-top: 1px solid var(--kn-line); } +.kn-projekt:last-child { border-bottom: 1px solid var(--kn-line); } +.kn-vorschau { position: relative; aspect-ratio: 16 / 10; overflow: hidden; border-radius: var(--kn-radius-lg); background: var(--kn-bg-subtle); box-shadow: var(--kn-ring); } +.kn-vorschau iframe { position: absolute; top: 0; left: 0; width: 1280px; height: 800px; border: 0; transform-origin: 0 0; pointer-events: none; } +.kn-vorschau-leer { display: grid; place-items: center; padding: 16px; text-align: center; color: var(--kn-text-2); } +.kn-projekt-koerper { min-width: 0; } +.kn-projekt-kopf { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; } +.kn-projekt-name { margin: 0; font-size: 18px; line-height: 26px; font-weight: 600; overflow-wrap: anywhere; } +.kn-projekt-links { display: flex; flex-wrap: wrap; gap: 8px; } + +/* Auftrag, Änderungen, Arbeiten, Aktivität */ +.kn-aktivitaet { margin: 0; } +.kn-auftrag-karte { padding: 16px 0; border-top: 1px solid var(--kn-line); } +.kn-auftrag-karte > .kn-auftrag + * { margin-top: 12px; } +.kn-auftrag-kopf { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; } +.kn-auftrag-name { min-width: 0; } +.kn-auftrag-nr { display: block; font-size: 12px; color: var(--kn-text-2); font-variant-numeric: tabular-nums; } +.kn-auftrag-name h3 { margin: 0; font-size: 16px; line-height: 24px; font-weight: 600; overflow-wrap: anywhere; } +.kn-auftrag-gesamt { margin: 8px 0 0; font-size: 13px; color: var(--kn-text-2); } +.kn-schritte { display: flex; gap: 4px; margin: 12px 0 0; padding: 0; list-style: none; } +.kn-schritte li { flex: 1; padding-top: 6px; border-top: 2px solid var(--kn-line); font-size: 12px; color: var(--kn-text-3); } +.kn-schritte li.is-done { border-top-color: var(--kn-text-2); color: var(--kn-text-2); } +.kn-schritte li.is-current { border-top-color: var(--kn-accent); color: var(--kn-text); font-weight: 600; } +.kn-auftrag-hinweis { margin: 12px 0 0; font-size: 13px; color: var(--kn-orange-fg); } +.kn-zeitleiste { margin: 0; } +.kn-tag { margin: 0; } +.kn-tag-name { margin: 16px 0 4px; font-size: 13px; font-weight: 600; color: var(--kn-text-2); } +.kn-aenderungen, .kn-arbeiten, .kn-unterstuetzung { list-style: none; margin: 0; padding: 0; } +.kn-aenderung { padding: 8px 0; border-top: 1px solid var(--kn-line); } +.kn-aenderung-kopf { display: flex; justify-content: space-between; gap: 12px; } +.kn-aenderung-kopf time { flex: none; font-size: 13px; color: var(--kn-text-2); font-variant-numeric: tabular-nums; } +.kn-aenderung-details, .kn-eintrag-aenderungen { margin: 4px 0 0; padding-left: 18px; color: var(--kn-text-2); font-size: 13px; } +.kn-eintrag { display: flex; justify-content: space-between; gap: 12px; padding: 10px 0; border-top: 1px solid var(--kn-line); } +.kn-eintrag-haupt { min-width: 0; flex: 1; } +.kn-eintrag-kopf { display: flex; flex-wrap: wrap; gap: 4px 8px; } +.kn-eintrag-meta { font-size: 13px; color: var(--kn-text-2); } +.kn-eintrag-text { margin: 4px 0 0; color: var(--kn-text-2); font-size: 13px; } +.kn-unterstuetzung li { display: flex; justify-content: space-between; gap: 12px; margin-top: 4px; font-size: 13px; color: var(--kn-text-2); } +.kn-dateien { margin: 0; } +.kn-details { margin: 0; } + +/* ------------------------------------------------------------------ Chat (Muster .woms-chat des Ticketsystems, Präfix .kn-chat) --- */ +/* chat-composer.css (Kopie) lädt vor diesem Blatt; jede Regel für Elemente des Bausteins ist hier strikt spezifischer (.kn .kn-chat …) und ohne !important. */ +.kn-chat { + --chat-accent: var(--kn-accent); + --chat-accent-hover: var(--kn-accent-hover); + --chat-accent-soft: var(--kn-accent-soft); + --chat-accent-line: var(--kn-accent-ring); + --chat-accent-text: var(--kn-accent); + --chat-on-accent: var(--kn-bg); + --chat-surface: var(--kn-bg); + --chat-line: var(--kn-line-strong); + --chat-muted: var(--kn-text-2); + --chat-bubble-in: var(--kn-bg-hover); + --chat-bubble-out: var(--kn-grau-bg); + --chat-track: var(--kn-spur); + display: flex; flex-direction: column; gap: var(--kn-sp-2); min-width: 0; +} +.kn .kn-chat .chat-box { display: flex; flex-direction: column; gap: var(--kn-sp-2); min-width: 0; } +.kn .kn-chat .chat-messages { display: flex; flex-direction: column; gap: 8px; min-height: 280px; max-height: 60vh; overflow-y: auto; padding: 4px 0; } +.kn .kn-chat .chat-bubble { + align-self: flex-start; max-width: 78%; padding: 8px 12px; border: 0; border-radius: var(--kn-radius-lg); + background: var(--chat-bubble-in); color: var(--kn-text); overflow-wrap: anywhere; +} +.kn .kn-chat .chat-bubble.mine { align-self: flex-end; background: var(--chat-bubble-out); } +.kn .kn-chat .chat-meta { display: block; margin-top: 4px; font-size: 12px; color: var(--kn-text-2); } +.kn .kn-chat .chat-att-file { border: 0; background: var(--kn-bg); box-shadow: var(--kn-ring); color: var(--kn-text); } +.kn .kn-chat .chat-att-file:hover { background: var(--kn-bg-hover); } +.kn .kn-chat .chat-audio-bar { background: var(--chat-track); } +.kn .kn-chat .chat-att-image:focus-visible, .kn .kn-chat .chat-audio-bar:focus-visible { outline: 2px solid var(--kn-accent); } +.kn .kn-chat .chat-bubble.mine .chat-audio-play { background: var(--chat-accent); color: var(--chat-on-accent); } +.kn .kn-chat .chat-audio-dl:hover { background: var(--kn-accent-soft); } +.kn .kn-chat .chat-composer-note, .kn .kn-chat .chat-tray-item .chat-file-size, .kn .kn-chat .chat-audio.is-error .chat-audio-time { color: var(--kn-rot-fg); } +.kn .kn-chat .chat-composer-note.is-info { color: var(--kn-text-2); } +.kn .kn-chat .chat-input-row textarea { + resize: none; min-height: 46px; max-height: 130px; padding: 11px 14px; border: 0; border-radius: var(--kn-radius-lg); + background: var(--kn-bg); color: var(--kn-text); box-shadow: var(--kn-ring); font: inherit; line-height: 1.4; +} +.kn .kn-chat .chat-input-row textarea:focus { outline: none; box-shadow: 0 0 0 1px var(--kn-accent), 0 0 0 4px var(--kn-accent-ring); } +.kn .kn-chat .chat-icon-btn { border: 0; background: var(--kn-bg); color: var(--kn-text); box-shadow: var(--kn-ring); } +.kn .kn-chat .chat-icon-btn:hover:not(:disabled) { background: var(--kn-bg-hover); } +.kn .kn-chat .chat-icon-btn[aria-expanded="true"], .kn .kn-chat .chat-icon-btn[aria-expanded="true"]:hover:not(:disabled) { background: var(--kn-accent-soft); box-shadow: 0 0 0 1px var(--kn-accent); } +.kn .kn-chat .chat-icon-btn:disabled, .kn .kn-chat .chat-send:disabled { opacity: .55; cursor: not-allowed; } +.kn .kn-chat .chat-send.is-busy:disabled { opacity: .8; cursor: progress; } +.kn .kn-chat .chat-rec { border-color: var(--kn-danger-ring); background: var(--kn-rot-bg); } +.kn .kn-chat .chat-rec .chat-icon-btn { background: transparent; box-shadow: none; } +.kn .kn-chat .chat-rec .chat-icon-btn:hover { background: var(--kn-accent-soft); } +.kn .kn-chat .chat-rec-stop, .kn .kn-chat .chat-rec-wave { color: var(--kn-danger); } +.kn .kn-chat .chat-rec-dot { background: var(--kn-danger); } +.kn .kn-chat .chat-menu { border: 0; border-radius: var(--kn-radius-lg); box-shadow: var(--kn-shadow-pop); background: var(--kn-bg); } +.kn .kn-chat .chat-menu button { border-radius: var(--kn-radius); color: var(--kn-text); } +.kn .kn-chat .chat-menu button:hover, .kn .kn-chat .chat-menu button:focus-visible { background: var(--kn-bg-hover); } +.kn .kn-chat .chat-menu button:focus-visible { outline: 2px solid var(--kn-accent); outline-offset: -2px; } +.kn .kn-chat .chat-tray-item { border: 1px solid transparent; background: var(--kn-bg-hover); } +.kn .kn-chat .chat-tray-item.is-error { border-color: var(--kn-danger); background: var(--kn-rot-bg); } +.kn .kn-chat.chat-drop-active::after, .kn .kn-chat .chat-box.chat-drop-active::after { background: var(--kn-ablage-hell); color: var(--kn-text); border-color: var(--kn-text-3); } +.kn .kn-chat .kn-chat-composer { margin-top: 0; } +.kn .kn-chat .kn-chat-kasten { position: relative; } +.kn .kn-chat .kn-chat-verlauf { scroll-behavior: auto; } +.kn .kn-chat .chat-composer { margin-top: 8px; } + +/* ------------------------------------------------------------------ Verträge (Rahmen, Reiter, Rechnungen) --- */ +.kn-reiter-inhalt { min-width: 0; } +.kn-reiter-abo { min-width: 0; } +.kn-kuendigen { margin: 40px 0 0; padding-top: 16px; border-top: 1px solid var(--kn-line); font-size: 14px; } +.kn-rechnungen-liste { margin: 0; } +.kn-rechnungen { margin: 0; } +.kn-rechnungen tr.is-bezahlt td { color: var(--kn-text-2); } +.kn-rechnung-nr { font-weight: 500; } +.kn-rechnung-projekt { display: block; margin-top: 2px; font-size: 12.5px; font-weight: 400; color: var(--kn-text-2); overflow-wrap: anywhere; } +.kn-rechnung-aktionen { white-space: nowrap; } +.kn-num { font-variant-numeric: tabular-nums; } +.kn-auftraege-rumpf { min-width: 0; } + +/* Abo, Vertrag */ +.kn-vertrag-kopf { display: flex; align-items: center; justify-content: space-between; gap: 12px; } +.kn-vertrag-kopf .kn-abschnitt { margin: 0 0 12px; } +.kn-widerruf { margin: 12px 0 0; font-size: 14px; } +.kn-gutschrift { margin: 24px 0 0; } +.kn-gutschrift-link { gap: 8px; } +.kn-wechsel { margin-top: 32px; padding-top: 24px; border-top: 1px solid var(--kn-line); } +.kn-abo-auswahl { display: flex; flex-direction: column; gap: 16px; } +.kn-abo-karten { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; } +.kn-abo-karte { padding: 16px 0; border-top: 1px solid var(--kn-line); } +.kn-abo-karte .kn-knopf-primaer { margin-top: 8px; } + +/* Stunden */ +.kn-statistik { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; margin: 0 0 16px; } +.kn-stat { padding: 12px 0; border-top: 1px solid var(--kn-line); } +.kn-stat strong { display: block; font-size: 20px; line-height: 28px; font-weight: 700; letter-spacing: -.01em; } +.kn-stat span { font-size: 13px; color: var(--kn-text-2); } +.kn-vorschuss { color: var(--kn-violett-fg); } + +/* E-Mail-Speicher */ +.kn-speicher-platz { margin-top: 16px; } +.kn-stepper { display: flex; align-items: center; gap: 12px; margin: 12px 0; } +.kn-stepper-zahl { font-variant-numeric: tabular-nums; } +.kn-stepper .kn-knopf { min-width: 36px; padding: 0 12px; } +.kn-speicher-summe { margin: 4px 0 12px; color: var(--kn-text-2); font-variant-numeric: tabular-nums; } +.kn-speicher-zusammenfassung { margin-top: 16px; } + +/* Bestellen und Wechsel: Zusammenfassung, Beginn-Auswahl, Haken */ +.kn-beginn { margin: 16px 0; padding: 0; border: 0; min-width: 0; } +.kn-beginn legend { padding: 0; margin: 0 0 4px; font-weight: 600; } +.kn-beginn .kn-check, .kn-panel > .kn-check { display: flex; align-items: flex-start; margin: 8px 0; color: var(--kn-text); } +.kn-panel .kn-check { align-items: flex-start; } +.kn-panel .kn-check input { margin-top: 3px; flex: 0 0 auto; } + +/* Übernahmegutschrift */ +.kn-tk-einleitung { margin: 0 0 16px; } +.kn-tk-form { display: flex; flex-direction: column; gap: 12px; margin: 16px 0 24px; max-width: 640px; } +.kn-tk-feld { display: flex; flex-direction: column; gap: 6px; min-width: 0; border: 0; padding: 0; margin: 0; } +.kn-tk-feld label, .kn-tk-feld legend { padding: 0; font-weight: 500; } +.kn-tk-feld small { font-weight: 400; color: var(--kn-text-2); } +.kn-tk-feld input:not([type="checkbox"]):not([type="file"]) { height: 36px; padding: 0 10px; border: 0; border-radius: var(--kn-radius); background: var(--kn-bg); box-shadow: var(--kn-ring); font: inherit; outline: none; } +.kn-tk-feld input:focus { box-shadow: 0 0 0 1px var(--kn-accent), 0 0 0 4px var(--kn-accent-ring); } +.kn-tk-zeile { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; } +.kn-tk-leistungen { display: flex; flex-wrap: wrap; gap: 8px 20px; } +.kn-tk-leistungen label { display: inline-flex; align-items: center; gap: 6px; font-weight: 400; cursor: pointer; } +.kn-tk-bedingungen summary { cursor: pointer; font-weight: 500; } +.kn-tk-bedingungen ol { margin: 8px 0 0; padding-left: 20px; color: var(--kn-text-2); font-size: 13px; } +.kn-tk-bedingungen li { margin: 4px 0; } +.kn-tk-liste { display: flex; flex-direction: column; } +.kn-tk-antrag { padding: 16px 0; border-top: 1px solid var(--kn-line); } +.kn-tk-kopf { display: flex; align-items: center; justify-content: space-between; gap: 12px; } +.kn-tk-frage { margin: 8px 0; padding: 12px; border-radius: var(--kn-radius); background: var(--kn-bg-subtle); } +.kn-tk-antwort { display: flex; flex-direction: column; gap: 8px; max-width: 520px; } + +/* Passwort ändern */ +.kn-pw { max-width: 420px; } +.kn-pw-form { display: flex; flex-direction: column; gap: 16px; } +.kn-pw-erfolg { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; } +.kn-pw-erfolg strong { color: var(--kn-gruen-fg); font-size: 16px; } +.kn-pw-erfolg p { margin: 0; color: var(--kn-text-2); } + +/* ------------------------------------------------------------------ Aufträge im Reiter Rechnungen (Alt-Modul auftrag-portal.js) --- */ +.kn .ab-portal { + --ab-karte: var(--kn-bg); --ab-text: var(--kn-text); --ab-weich: var(--kn-text-2); --ab-linie: var(--kn-line); --ab-box: var(--kn-bg-subtle); + --ab-knopf: var(--kn-accent); --ab-knopf-text: var(--kn-bg); --ab-fokus: var(--kn-accent); + --ab-warn-bg: var(--kn-orange-bg); --ab-warn-text: var(--kn-orange-fg); --ab-ok-bg: var(--kn-gruen-bg); --ab-ok-text: var(--kn-gruen-fg); + --ab-rot-bg: var(--kn-rot-bg); --ab-rot-text: var(--kn-rot-fg); --ab-muted-bg: var(--kn-grau-bg); --ab-muted-text: var(--kn-grau-fg); --ab-fehler: var(--kn-danger); + margin: 0 0 28px; color: var(--ab-text); +} +.kn .ab-portal h2 { margin: 0 0 6px; } +.kn .ab-portal .ab-karte { margin: 12px 0 0; padding: 16px 0; border-top: 1px solid var(--ab-linie); overflow-wrap: anywhere; } +.kn .ab-portal .ab-karte-kopf { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; } +.kn .ab-portal .ab-karte-kopf .ab-pille { margin: 0; flex: none; } +.kn .ab-portal .ab-karte-kopf p { margin: 0; } +.kn .ab-portal .ab-karte-nummer strong { font-weight: 600; font-variant-numeric: tabular-nums; } +.kn .ab-portal .ab-karte-titel { margin-top: 2px; } +.kn .ab-portal .ab-karte-meta { margin-top: 2px; font-size: 13px; color: var(--ab-weich); } +.kn .ab-portal .ab-karte-aktionen { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px; } +.kn .ab-portal .ab-karte-aktionen a { + display: inline-flex; align-items: center; min-height: 36px; padding: 0 12px; border-radius: var(--kn-radius); box-shadow: var(--kn-ring); + color: var(--ab-text); text-decoration: none; font-weight: 500; font-size: 14px; +} +.kn .ab-portal .ab-details > summary { cursor: pointer; display: flex; align-items: center; min-height: 36px; color: var(--ab-text); font-weight: 600; } +.kn .ab-portal .ab-details[open] > summary { margin-bottom: 4px; } +.kn .ab-portal .ab-abschnitt { margin: 0; } +.kn .ab-portal .ab-zusammenfassung { margin: 0; } +.kn .ab-portal .ab-link { text-underline-offset: 3px; } +.kn .ab-portal .ab-pille { + display: inline-block; margin: 0 0 12px; padding: 2px 8px; border-radius: 4px; font-size: 12px; font-weight: 500; line-height: 20px; + background: var(--ab-muted-bg); color: var(--ab-muted-text); +} +.kn .ab-portal .ab-pille-warn { background: var(--ab-warn-bg); color: var(--ab-warn-text); } +.kn .ab-portal .ab-pille-ok { background: var(--ab-ok-bg); color: var(--ab-ok-text); } +.kn .ab-portal .ab-pille-rot { background: var(--ab-rot-bg); color: var(--ab-rot-text); } +.kn .ab-portal .ab-text { white-space: pre-line; margin: 0 0 16px; } +.kn .ab-portal .ab-h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .06em; font-weight: 600; color: var(--ab-weich); margin: 24px 0 8px; } +.kn .ab-portal .ab-liste { margin: 0 0 8px; padding: 0; } +.kn .ab-portal .ab-zeile { display: grid; grid-template-columns: 9rem 1fr; gap: 4px 12px; padding: 8px 0; border-top: 1px solid var(--ab-linie); } +.kn .ab-portal .ab-zeile dt { color: var(--ab-weich); } +.kn .ab-portal .ab-zeile dd { margin: 0; } +.kn .ab-portal .ab-gesamt { display: flex; justify-content: space-between; gap: 12px; margin: 12px 0 8px; padding-top: 12px; border-top: 2px solid var(--ab-text); } +.kn .ab-portal .ab-gesamt strong { font-weight: 700; font-variant-numeric: tabular-nums; } +.kn .ab-portal .ab-hinweis { margin: 4px 0 0; font-size: 13px; color: var(--ab-weich); } +.kn .ab-portal .ab-links { list-style: none; margin: 0 0 16px; padding: 0; } +.kn .ab-portal .ab-links a { display: flex; align-items: center; min-height: 36px; line-height: 1.35; padding: 4px 0; font-weight: 600; } +.kn .ab-portal .ab-bestaetigt { background: var(--ab-ok-bg); color: var(--ab-ok-text); border-radius: var(--kn-radius); padding: 12px 16px; margin: 0 0 16px; } +.kn .ab-portal .ab-bestaetigt p { margin: 2px 0; } +.kn .ab-portal .ab-bestaetigt:focus-visible { outline: 2px solid var(--ab-fokus); outline-offset: 2px; } +.kn .ab-portal .ab-tabelle { width: 100%; border-collapse: collapse; font-size: 14px; font-variant-numeric: tabular-nums; } +.kn .ab-portal .ab-tabelle th { text-align: left; font-weight: 500; color: var(--ab-weich); padding: 6px 8px 6px 0; border-bottom: 1px solid var(--ab-linie); } +.kn .ab-portal .ab-tabelle td { padding: 8px 8px 8px 0; border-bottom: 1px solid var(--ab-linie); vertical-align: top; } +.kn .ab-portal .ab-tabelle th:nth-child(n+2), .kn .ab-portal .ab-tabelle td:nth-child(n+2) { text-align: right; white-space: nowrap; } +.kn .ab-portal .ab-bestaetigen { margin-top: 24px; padding-top: 8px; border-top: 1px solid var(--ab-linie); } +.kn .ab-portal .ab-feldgruppe { border: 0; margin: 0; padding: 0; min-width: 0; } +.kn .ab-portal .ab-feldgruppe legend { font-weight: 600; padding: 0; margin: 12px 0 4px; } +.kn .ab-portal .ab-haken { display: flex; gap: 12px; align-items: flex-start; padding: 10px 0; min-height: 44px; font-size: 15px; cursor: pointer; } +.kn .ab-portal .ab-haken input { flex: 0 0 22px; width: 22px; height: 22px; margin: 1px 0 0; accent-color: var(--ab-knopf); } +.kn .ab-portal .ab-frei { color: var(--ab-weich); font-size: .85em; } +.kn .ab-portal .ab-fehler { margin: 8px 0; color: var(--ab-fehler); font-weight: 600; } +.kn .ab-portal .ab-fehler[hidden] { display: none; } +.kn .ab-portal .ab-knopf { + display: inline-block; min-height: 36px; padding: 0 16px; border: 0; border-radius: var(--kn-radius); cursor: pointer; + background: var(--ab-knopf); color: var(--ab-knopf-text); font: inherit; font-weight: 600; box-shadow: var(--kn-shadow-btn); +} +.kn .ab-portal .ab-knopf:disabled { opacity: .45; cursor: default; } +.kn .ab-portal .ab-knopf:focus-visible, .kn .ab-portal .ab-link:focus-visible { outline: 2px solid var(--ab-fokus); outline-offset: 2px; } +.kn .ab-portal .ab-notiz, .kn .ab-portal .ab-gesperrt { margin: 12px 0 0; font-size: 13px; color: var(--ab-weich); } + +/* ------------------------------------------------------------------ Handy --- */ +@media (max-width: 880px), (pointer: coarse) and (max-height: 500px) { + .kn-projekt { grid-template-columns: minmax(0, 1fr); gap: 16px; padding: 16px 0; } + .kn-fakten { grid-template-columns: 1fr; gap: 0; } + .kn-fakten dd { margin-bottom: 6px; } + .kn-zusammenfassung td { display: block; width: auto; padding: 6px 0; border-top: 0; } + .kn-zusammenfassung td:first-child { width: auto; padding-top: 12px; border-top: 1px solid var(--kn-line); } + .kn-zusammenfassung tr:last-child td { border-bottom: 0; } + .kn-abo-karten { grid-template-columns: minmax(0, 1fr); gap: 8px; } + .kn-statistik { grid-template-columns: minmax(0, 1fr); gap: 0; } + .kn-zeilenlink { display: inline-flex; align-items: center; min-height: 44px; } + .kn-pw { max-width: none; } + .kn-tk-feld input:not([type="checkbox"]):not([type="file"]) { height: 44px; font-size: 16px; } + .kn-stepper .kn-knopf { min-width: 44px; } + .kn .kn-chat .chat-bubble { max-width: 88%; } + .kn .kn-chat .chat-messages { min-height: 240px; max-height: 55vh; } + .kn .kn-chat .chat-input-row textarea { font-size: 16px; } + .kn .ab-portal .ab-karte-aktionen a, .kn .ab-portal .ab-knopf, .kn .ab-portal .ab-details > summary, .kn .ab-portal .ab-links a { min-height: 44px; } + .kn .ab-portal .ab-knopf { width: 100%; } + .kn .ab-portal .ab-zeile { grid-template-columns: 1fr; gap: 0; } + /* Rechnungen als gestapelte Zeilen */ + .kn-rechnungen thead, .kn .ab-portal .ab-tabelle thead { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; } + .kn-rechnungen table, .kn-rechnungen tbody, .kn-rechnungen tr, .kn-rechnungen td, + .kn .ab-portal .ab-tabelle, .kn .ab-portal .ab-tabelle tbody, .kn .ab-portal .ab-tabelle tr, .kn .ab-portal .ab-tabelle td { display: block; } + .kn-rechnungen tr, .kn .ab-portal .ab-tabelle tr { padding: 8px 0; border-bottom: 1px solid var(--kn-line); } + .kn-rechnungen td, .kn .ab-portal .ab-tabelle td { display: flex; justify-content: space-between; gap: 12px; padding: 2px 0; border: 0; text-align: right; white-space: normal; } + .kn-rechnungen td:first-child, .kn .ab-portal .ab-tabelle td:first-child { display: block; text-align: left; font-weight: 600; } + .kn-rechnungen td::before, .kn .ab-portal .ab-tabelle td:nth-child(n+2)::before { content: attr(data-label); color: var(--kn-text-2); text-align: left; } + .kn-rechnungen td:first-child::before { content: none; } + .kn-rechnungen .kn-rechnung-aktionen { justify-content: flex-start; padding-top: 6px; } + .kn-rechnungen .kn-rechnung-aktionen::before { content: none; } +} diff --git a/public/neu/oeffentlich/oeffentlich.css b/public/neu/oeffentlich/oeffentlich.css index fcfd403..ae4b700 100644 --- a/public/neu/oeffentlich/oeffentlich.css +++ b/public/neu/oeffentlich/oeffentlich.css @@ -8,7 +8,7 @@ .kn-seite .hidden { display: none !important; } .kn-seite a { color: inherit; text-decoration: underline; text-decoration-color: var(--kn-line-strong); text-underline-offset: 3px; } .kn-seite a:hover { text-decoration-color: currentColor; } -.kn-seite :focus-visible { outline: 2px solid #111; outline-offset: 2px; } +.kn-seite :focus-visible { outline: 2px solid var(--kn-accent); outline-offset: 2px; } .kn-seite :disabled { opacity: .55; cursor: not-allowed; } .kn-seite .kn-fehlertext { margin: 0; font-size: 14px; line-height: 20px; color: var(--kn-danger); font-weight: 600; } @@ -32,7 +32,7 @@ .kn-login-staff { display: block; text-align: center; font-size: 14px; font-weight: 600; } .kn-login-knopf { position: relative; display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; min-height: 44px; margin-top: 4px; - border: 0; border-radius: var(--kn-radius); background: var(--kn-accent); color: #fff; font: inherit; font-weight: 600; cursor: pointer; + border: 0; border-radius: var(--kn-radius); background: var(--kn-accent); color: var(--kn-bg); font: inherit; font-weight: 600; cursor: pointer; box-shadow: var(--kn-shadow-btn); transition: background-color .12s; } .kn-login-knopf:hover:not(:disabled) { background: var(--kn-accent-hover); } @@ -74,7 +74,7 @@ .kn-recht .kn-fehlertext { margin: 16px 0 0; } .kn-absenden { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; margin-top: 20px; padding: 0 24px; border: 0; border-radius: var(--kn-radius); - background: var(--kn-accent); color: #fff; font: inherit; font-weight: 600; cursor: pointer; box-shadow: var(--kn-shadow-btn); + background: var(--kn-accent); color: var(--kn-bg); font: inherit; font-weight: 600; cursor: pointer; box-shadow: var(--kn-shadow-btn); } .kn-absenden:hover:not(:disabled) { background: var(--kn-accent-hover); } .kn-bestaetigung { margin: 24px 0; padding-top: 24px; border-top: 1px solid var(--kn-line); } @@ -103,11 +103,34 @@ } /* ------------------------------------------------------------------ Auftragsbestätigung (auftrag.html, Link aus der Mail) --- */ +/* body.ab-seite trägt kein .kn: die Tokens aus tokens.css stehen deshalb hier noch einmal (gleiche Werte), die --ab-* leiten sich daraus ab */ body.ab-seite { - --ab-grund: #ffffff; --ab-karte: #ffffff; --ab-text: #1a1f36; --ab-weich: #5e6678; --ab-linie: #ebeef1; --ab-box: #f8f9fb; - --ab-knopf: #111111; --ab-knopf-text: #ffffff; --ab-fokus: #111111; - --ab-warn-bg: #fcedb9; --ab-warn-text: #a82c00; --ab-ok-bg: #d7f7c2; --ab-ok-text: #006908; - --ab-rot-bg: #ffe7f2; --ab-rot-text: #b3093c; --ab-muted-bg: #ebeef1; --ab-muted-text: #414552; --ab-fehler: #c0123c; + --kn-bg: #fff; + --kn-bg-hover: #f6f8fa; + --kn-bg-subtle: #f8f9fb; + --kn-text: #1a1f36; + --kn-text-2: #5e6678; + --kn-text-3: #8a93a3; + --kn-line: #ebeef1; + --kn-line-strong: #d8dde3; + --kn-accent: #111; + --kn-grau-bg: #ebeef1; + --kn-grau-fg: #414552; + --kn-orange-bg: #fcedb9; + --kn-orange-fg: #a82c00; + --kn-gruen-bg: #d7f7c2; + --kn-gruen-fg: #006908; + --kn-rot-bg: #ffe7f2; + --kn-rot-fg: #b3093c; + --kn-danger: #c0123c; + --kn-radius: 6px; + --kn-radius-lg: 12px; + --kn-shadow-btn: 0 1px 1px rgba(0,0,0,.12), 0 2px 5px rgba(60,66,87,.08); + --kn-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + --ab-grund: var(--kn-bg); --ab-karte: var(--kn-bg); --ab-text: var(--kn-text); --ab-weich: var(--kn-text-2); --ab-linie: var(--kn-line); --ab-box: var(--kn-bg-subtle); + --ab-knopf: var(--kn-accent); --ab-knopf-text: var(--kn-bg); --ab-fokus: var(--kn-accent); + --ab-warn-bg: var(--kn-orange-bg); --ab-warn-text: var(--kn-orange-fg); --ab-ok-bg: var(--kn-gruen-bg); --ab-ok-text: var(--kn-gruen-fg); + --ab-rot-bg: var(--kn-rot-bg); --ab-rot-text: var(--kn-rot-fg); --ab-muted-bg: var(--kn-grau-bg); --ab-muted-text: var(--kn-grau-fg); --ab-fehler: var(--kn-danger); color-scheme: light; } @@ -115,7 +138,7 @@ body.ab-seite { body.ab-seite { margin: 0; min-height: 100dvh; display: flex; flex-direction: column; background: var(--ab-grund); color: var(--ab-text); - font: 16px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; -webkit-text-size-adjust: 100%; -webkit-font-smoothing: antialiased; + font: 16px/1.55 var(--kn-font); -webkit-text-size-adjust: 100%; -webkit-font-smoothing: antialiased; } .ab-seite .hidden { display: none !important; } .ab-seite .ab-innen { max-width: 40rem; margin: 0 auto; padding: 0 16px; box-sizing: border-box; width: 100%; } @@ -162,7 +185,7 @@ body.ab-seite { .ab-seite .ab-links { list-style: none; margin: 0 0 16px; padding: 0; } .ab-seite .ab-links a { display: flex; align-items: center; min-height: 44px; line-height: 1.35; padding: 4px 0; font-weight: 600; } .ab-seite .ab-bestaetigt { - background: var(--ab-ok-bg); color: var(--ab-ok-text); border-radius: var(--kn-radius, 6px); padding: 12px 16px; margin: 0 0 16px; + background: var(--ab-ok-bg); color: var(--ab-ok-text); border-radius: var(--kn-radius); padding: 12px 16px; margin: 0 0 16px; } .ab-seite .ab-bestaetigt p { margin: 2px 0; } .ab-seite .ab-bestaetigt:focus-visible { outline: 2px solid var(--ab-fokus); outline-offset: 2px; } @@ -198,8 +221,8 @@ body.ab-seite { .ab-seite .ab-fehler { margin: 8px 0; color: var(--ab-fehler); font-weight: 600; } .ab-seite .ab-fehler[hidden] { display: none; } .ab-seite .ab-knopf { - display: inline-block; min-height: 44px; padding: 0 24px; border: 0; border-radius: var(--kn-radius, 6px); cursor: pointer; - background: var(--ab-knopf); color: var(--ab-knopf-text); font: inherit; font-weight: 600; box-shadow: 0 1px 1px rgba(0, 0, 0, .12), 0 2px 5px rgba(60, 66, 87, .08); + display: inline-block; min-height: 44px; padding: 0 24px; border: 0; border-radius: var(--kn-radius); cursor: pointer; + background: var(--ab-knopf); color: var(--ab-knopf-text); font: inherit; font-weight: 600; box-shadow: var(--kn-shadow-btn); } .ab-seite .ab-knopf:disabled { opacity: 0.45; cursor: default; } .ab-seite .ab-knopf:focus-visible, .ab-seite .ab-link:focus-visible { outline: 2px solid var(--ab-fokus); outline-offset: 2px; } diff --git a/test/knAnsichtStil.test.js b/test/knAnsichtStil.test.js new file mode 100644 index 0000000..746498c --- /dev/null +++ b/test/knAnsichtStil.test.js @@ -0,0 +1,29 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { readFileSync, existsSync } from 'node:fs' +import { K } from '../public/neu/ansicht-hilfen.js' + +const css = (f) => readFileSync(new URL(`../public/neu/${f}`, import.meta.url), 'utf8') +const lese = (p) => readFileSync(new URL(`../public/${p}`, import.meta.url), 'utf8') +const A = css('ansichten.css'); const O = css('oeffentlich/oeffentlich.css'); const C = css('chat-composer.css'); const BASIS = css('basis.css'); const TOKENS = css('tokens.css') +const MODULE = ['ansicht-hilfen', 'rechnung-offen', 'install-hinweis', 'website-details', 'ansicht-website', 'ansicht-chat', 'ansicht-vertraege', 'vertraege-rechnungen', 'vertraege-abo', 'ansicht-stunden', 'ansicht-speicher', 'ansicht-bestellen', 'ansicht-wechsel', 'ansicht-gutschrift', 'ansicht-passwort'] +const existiert = (n) => existsSync(new URL(`../public/neu/${n}.js`, import.meta.url)) +const lies = (n) => css(`${n}.js`).replace(/\/\*[\s\S]*?\*\//g, '') +const VERBOTEN = /linear-gradient|radial-gradient|conic-gradient|backdrop-filter|prefers-color-scheme:\s*dark|font-weight:\s*800|#9d3dfe|rgba?\(\s*157\s*,\s*61/i +test('Stil: keine verbotenen Mittel (Verläufe, Glas, Dunkelmodus, Lila, Gewicht 800), auch nicht in der Kopie des Composer-CSS', () => { for (const [n, c] of [['ansichten.css', A], ['oeffentlich.css', O], ['chat-composer.css', C]]) assert.doesNotMatch(c, VERBOTEN, n) }) +test('Stil: Farbliterale nur in Custom-Property-Deklarationen, jedes benutzte Token ist definiert', () => { + for (const [n, c] of [['ansichten.css', A], ['oeffentlich.css', O]]) for (const z of c.split('\n')) if (!/^\s*--kn-[\w-]+:/.test(z)) assert.doesNotMatch(z, /#[0-9a-f]{3,8}\b|rgba?\(/i, `${n}: ${z.trim()}`) + const definiert = new Set([...(TOKENS + A + O).matchAll(/(--kn-[\w-]+)\s*:/g)].map((m) => m[1])) + for (const m of (A + O).matchAll(/var\((--kn-[\w-]+)/g)) assert.ok(definiert.has(m[1]), m[1]) +}) +test('Stil: jede kn-Klasse der P2-Module hat eine Regel', () => { + const klassen = new Set(); for (const n of MODULE.filter(existiert)) for (const m of lies(n).matchAll(/class:\s*'([^']+)'/g)) m[1].split(/\s+/).filter((k) => k.startsWith('kn-')).forEach((k) => klassen.add(k)) + for (const wert of Object.values(K).flatMap((v) => (typeof v === 'string' ? [v] : Object.values(v)))) wert.split(/\s+/).filter((k) => k.startsWith('kn-')).forEach((k) => klassen.add(k)) + for (const k of klassen) assert.match(BASIS + A, new RegExp(`\\.${k}(?![\\w-])`), `Klasse ohne Regel: ${k}`) +}) +test('Stil: Aufträge-Abschnitt, Chat-Variablen, hidden und Handy-Abfrage', () => { + const ab = new Set([...(lese('auftrag-dom.js') + lese('auftrag-portal.js')).matchAll(/\bab-[a-z0-9]+(?:-[a-z0-9]+)*/g)].map((m) => m[0])) + for (const k of [...ab, 'ab-pille-warn', 'ab-pille-ok', 'ab-pille-rot'].filter((x) => x !== 'ab-portal')) assert.match(A, new RegExp(`\\.kn \\.ab-portal[^{]*\\.${k}(?![\\w-])`), k) + for (const v of ['accent', 'accent-hover', 'accent-soft', 'accent-line', 'accent-text', 'on-accent', 'line', 'muted', 'surface']) assert.match(A, new RegExp(`\\.kn-chat[^{]*\\{[^}]*--chat-${v}:`), v) + assert.match(A, /\.kn \.hidden\s*\{[^}]*display:\s*none/); assert.ok(A.includes('(max-width: 880px), (pointer: coarse) and (max-height: 500px)')) +})