Plan Checker, Wochenbericht-Fokus-Modus und Wandtafel-Dashboard-Feinschliff

Plan Checker (neues Studenten-Werkzeug, Seitenleiste + Admin → Plan-Checker-
Merkmale): lädt einen Bauplan als PDF hoch und lässt ChatGPT oder Claude ihn
gegen eine gepflegte Merkmalsliste prüfen. PDF-Rendering (pdf.js +
@napi-rs/canvas) berücksichtigt jetzt Ebenen (Optional Content Groups) und
WASM-Decoder korrekt, sendet eine Übersicht plus mehrere hochaufgelöste
Kachel-Ausschnitte statt eines einzelnen, zu groben Bilds, und beide Anbieter
bekommen dasselbe Bildmaterial. Jedes Prüfergebnis kann auf eines der
gesendeten Bilder als Beleg verweisen (bildIndex) — als Screenshot im
Ergebnis anklickbar, öffnet vergrößert in einem Modal statt eines von Chrome
blockierten target=_blank-Links auf eine data:-URL. KI-Modellname ist jetzt
pro Konto überschreibbar (Einstellungen → Mein Konto) für Drittanbieter mit
abweichendem Modell-Katalog.

Wochenbericht: dichte Kartenansicht bekommt einen Fokus-Modus — nur der
Kommentar liegt offen, Leistungsphasen und Beteiligte stecken in
aufklappbaren Abschnitten statt dauerhaft nebeneinander zu stehen.

Projektbereich: Terminplan-Auslastung rechnet jetzt exakt wie der
Auslastungs-Tab/die Wandtafel (statt einer unabhängigen, tageweisen
Schätzung); die LPH-Fortschrittsliste im Dashboard zeigt nur noch die
beauftragten Leistungsphasen.

Wandtafel: Dashboard-Kopf mit Kennzahlen-Reihe und Panel-Reihe neu
strukturiert, in den Blautönen des Firmenlogos; dieselbe Palette wurde
anschließend auf den gesamten App-weiten Dark Mode übernommen.

Dateibereinigung: nicht mehr benötigte Test-Uploads/Backups entfernt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
erikandClaude Sonnet 5 committed 2026-10-05 14:01:41 +02:00
1 parent db63a29365
commit 6ba93b2a92
308 files changed
+64441 -743

No files matched your search

+505 -353
View File
@@ -309,35 +309,91 @@ button.menu-item:hover { background: var(--surface-sunk); }
#sidebarCollapseIcon { transition: transform .2s var(--ease); }
/* === Wandtafel (js/wand/wand.js) ===
Zweispaltiges Touch-Dashboard für ein dauerhaft laufendes Display,
gewerke- und nutzerübergreifend — ersetzt den früheren Show Mode, der nur
die gerade angemeldete Sitzung großflächig klonte (keine Aggregation über
Gewerke oder Nutzer hinweg, siehe CLAUDE.md). Seitenleiste und Kopfzeile
blendet showTab() über body.wandtafel-aktiv aus: Das Kiosk-Konto ist ein
/* === Wandtafel / Dashboard (js/wand/wand.js) ===
Touch-Dashboard für ein dauerhaft laufendes Display, gewerke- und
nutzerübergreifend — ersetzt den früheren Show Mode, der nur die gerade
angemeldete Sitzung großflächig klonte (keine Aggregation über Gewerke
oder Nutzer hinweg, siehe CLAUDE.md). Seitenleiste und Kopfzeile blendet
showTab() über body.wandtafel-aktiv aus: Das Kiosk-Konto ist ein
gewöhnliches Mitgliedskonto mit echten Schreibrechten — die Kachelfläche
soll die einzige Berührfläche sein, damit eine zufällige Berührung nicht
in einen bearbeitbaren Bereich wie "Projekte" führt.
Verhält sich zusätzlich wie ein fester Bildschirmausschnitt statt einer
normal scrollenden Seite (height:100vh + overflow:hidden auf dem Weg
.app-main → .container → #wand) — ein unbeaufsichtigtes Display soll von
selbst nie scrollen müssen; die zwei Spalten strecken sich über die volle
restliche Höhe.
Aufbau (Eriks Wahl aus dem Vergleich mehrerer Gesamtentwürfe, "Leitstand"-
Struktur, später in Eriks eigener Logo-Farbwelt umgesetzt): Kopf
(Marke "Dashboard" + Sitzung/Uhrzeit + Abwesend-Kürzel) → eine
Kennzahlen-Reihe aus vier Kacheln (Auslastung gesamt, Besprochen gesamt,
offene Aufgaben, überfällig) → eine Reihe aus drei Panels (Auslastung je
Gewerk, Besprochen je Gewerk, Aktuell). Verhält sich zusätzlich wie ein
fester Bildschirmausschnitt statt einer normal scrollenden Seite
(height:100vh + overflow:hidden auf dem Weg .app-main → .container →
#wand) — ein unbeaufsichtigtes Display soll von selbst nie scrollen
müssen; Kopf, Kennzahlen-Reihe und Panel-Reihe strecken sich über die
volle restliche Höhe.
AUSNAHME, auf Eriks Wunsch (echter Touch-Monitor, "ich kann mich vor die
Tafel stellen und scrollen"): unterhalb der zwei Spalten folgt die
vollständige Projektliste (wandProjektlisteHtml(), js/wand/wand.js), und
NUR dieser Bereich (#wandContent, Klasse .wand-scrollable, von
renderWandboard() gesetzt) scrollt — eine offene Übersicht bleibt weiter
exakt einen Bildschirm hoch, siehe .wand-uebersicht unten. Ohne Berührung
kehrt die Ansicht nach WAND_IDLE_MS von selbst an den Anfang zurück
Tafel stellen und scrollen"): unterhalb davon folgt die vollständige
Projektliste (wandProjektlisteHtml(), js/wand/wand.js), und NUR dieser
Bereich (#wandContent, Klasse .wand-scrollable, von renderWandboard()
gesetzt) scrollt — eine offene Übersicht bleibt weiter exakt einen
Bildschirm hoch, siehe .wand-uebersicht unten. Ohne Berührung kehrt die
Ansicht nach WAND_IDLE_MS von selbst an den Anfang zurück
(wandAktivitaetErkannt()/wandBeiUntaetigkeitZuruecksetzen()). */
body.wandtafel-aktiv .app-topbar,
body.wandtafel-aktiv .side-menu,
body.wandtafel-aktiv .side-menu-backdrop { display: none !important; }
body.wandtafel-aktiv .app-main { height: 100vh; overflow: hidden; }
body.wandtafel-aktiv .container { height: 100%; overflow: hidden; display: flex; flex-direction: column; padding: 14px 20px; }
body.wandtafel-aktiv .container {
height: 100%; overflow: hidden; display: flex; flex-direction: column; padding: 14px 20px;
background: var(--dash-bg);
}
body.wandtafel-aktiv #wand { display: flex !important; flex-direction: column; flex: 1; min-height: 0; }
/* Eigene Farbwelt der Wandtafel — bewusst GETRENNT von --accent/--surface der
übrigen App: Eriks Wahl ("Pastell Freundlich", aus dem Design-Vergleich der
Gesamtentwürfe) nutzt die zwei Farben des Firmenlogos (aus public/logo.ico
ausgelesen: #94b0cc helles, #116896 dunkles Blau) statt des generischen
App-Akzents. Jede Fläche (Kennzahlen, Panels) trägt eine eigene, davon
abgeleitete Tönung. Rot/Gelb bleiben als Warn-/Kritisch-Farben eigenständig
(müssen unabhängig von der Markenfarbe sofort erkennbar bleiben), nur
gedeckter gewählt, damit sie zum Blau passen statt dagegen zu stehen.
--dash-hover ist eine reine Hervorhebung beim Antippen (kein Ersatz einer
bestehenden Flächenfarbe), deshalb für beide Modi eigens abgestimmt statt
eines einzigen hartkodierten Werts. */
body.wandtafel-aktiv {
--dash-bg: #f7fafc;
--dash-surface: #ffffff;
--dash-ink: #116896;
--dash-ink-soft: #94b0cc;
--dash-text: #20282e;
--dash-text-muted: #5d7488;
--dash-text-subtle: #8a9aa6;
--dash-panel-1: #dde9f1;
--dash-panel-2: #cfe0ea;
--dash-panel-3: #e7eff5;
--dash-kpi-4: #f6e3e0;
--dash-warn: #9a5a1f;
--dash-crit: #b8463f;
--dash-line: #e7eef3;
--dash-hover: rgba(17, 104, 150, .07);
}
@media (prefers-color-scheme: dark) {
html:not([data-theme="light"]) body.wandtafel-aktiv {
--dash-bg: #10181f; --dash-surface: #141c23;
--dash-ink: #7fc3dd; --dash-ink-soft: #4a7691;
--dash-text: #e7eef3; --dash-text-muted: #9fb3c0; --dash-text-subtle: #70838f;
--dash-panel-1: #16222c; --dash-panel-2: #141e27; --dash-panel-3: #131b22; --dash-kpi-4: #2a1f1c;
--dash-warn: #e8a15c; --dash-crit: #ef6f63;
--dash-line: #223039; --dash-hover: rgba(255, 255, 255, .06);
}
}
html[data-theme="dark"] body.wandtafel-aktiv {
--dash-bg: #10181f; --dash-surface: #141c23;
--dash-ink: #7fc3dd; --dash-ink-soft: #4a7691;
--dash-text: #e7eef3; --dash-text-muted: #9fb3c0; --dash-text-subtle: #70838f;
--dash-panel-1: #16222c; --dash-panel-2: #141e27; --dash-panel-3: #131b22; --dash-kpi-4: #2a1f1c;
--dash-warn: #e8a15c; --dash-crit: #ef6f63;
--dash-line: #223039; --dash-hover: rgba(255, 255, 255, .06);
}
.wand-kopfzeile {
flex-shrink: 0;
display: flex;
@@ -371,168 +427,124 @@ body.wandtafel-aktiv #wand { display: flex !important; flex-direction: column; f
Block-Scrollcontainer um, dessen erstes Kind (.wand-ambient) weiterhin
genau einen Bildschirm hoch ist — die Projektliste danach verlängert den
Inhalt darüber hinaus und erzeugt dadurch den Scrollbalken. */
.wand-grid-wrap { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.wand-grid-wrap { flex: 1; min-height: 0; display: flex; flex-direction: column; background: var(--dash-bg); }
.wand-grid-wrap.wand-scrollable { display: block; overflow-y: auto; }
.wand-ambient { min-height: 100%; display: flex; flex-direction: column; }
.wand-c-layout { flex: 1; min-height: 0; display: grid; grid-template-columns: 320px 1fr; gap: 16px; }
.wand-c-links { display: flex; flex-direction: column; gap: 16px; min-height: 0; }
.wand-c-links .wand-kachel { flex: 1; min-height: 0; }
.wand-c-rechts {
background: var(--surface);
border: 1px solid var(--line-soft);
border-radius: var(--r-xl);
box-shadow: var(--sh-2);
padding: 18px 22px;
display: flex;
flex-direction: column;
min-height: 0;
/* ===== Kopf: Marke "Dashboard" + Sitzung/Uhrzeit + Abwesend-Mini ===== */
.dash-kopf { flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; }
.dash-marke { font-family: 'Fredoka', sans-serif; font-weight: 600; font-size: 19px; color: var(--dash-ink); }
.dash-kopf-rechts { display: flex; align-items: center; gap: 14px; }
.dash-datum { font-size: 12.5px; color: var(--dash-text-muted); background: var(--dash-panel-3); padding: 4px 12px; border-radius: var(--r-pill); }
.dash-abwesend-mini { display: flex; }
.dash-abwesend-mini.ist-klickbar { cursor: pointer; border-radius: var(--r-pill); }
.dash-abwesend-mini.ist-klickbar:hover { filter: brightness(.96); }
.dash-avatar-mini {
width: 25px; height: 25px; border-radius: 50%; background: var(--dash-panel-2); color: var(--dash-ink);
font-size: 9.5px; font-weight: 800; display: flex; align-items: center; justify-content: center;
margin-left: -7px; border: 2px solid var(--dash-bg);
}
.dash-avatar-mini:first-child { margin-left: 0; }
.wand-kachel {
background: var(--surface);
border: 1px solid var(--line-soft);
border-radius: var(--r-xl);
box-shadow: var(--sh-2);
padding: 18px 20px;
display: flex;
flex-direction: column;
min-height: 0;
}
.wand-kachel-titel {
margin: 0 0 12px;
font-size: 13px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .05em;
color: var(--text-muted);
flex-shrink: 0;
}
/* Der Inhalt verteilt sich GLEICHMÄSSIG über die volle, gestreckte
Kachelhöhe (space-evenly) statt oben zu kleben und unten Leerraum zu
lassen — genau das ließ vorher dünn befüllte Kacheln neben vollen leer
wirken. */
.wand-kachel-body { flex: 1; display: flex; flex-direction: column; justify-content: space-evenly; min-height: 0; overflow: hidden; }
/* ===== Kennzahlen-Reihe (4 Kacheln) ===== */
.dash-kpi-reihe { flex-shrink: 0; display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-bottom: 18px; }
.dash-kpi { border-radius: var(--r-xl); padding: 16px 18px; }
.dash-kpi:nth-child(1) { background: var(--dash-panel-1); }
.dash-kpi:nth-child(2) { background: var(--dash-panel-2); }
.dash-kpi:nth-child(3) { background: var(--dash-panel-3); }
.dash-kpi:nth-child(4) { background: var(--dash-kpi-4); }
.dash-kpi.ist-klickbar { cursor: pointer; }
.dash-kpi.ist-klickbar:hover { filter: brightness(.96); }
.dash-kpi-label { font-size: 11px; font-weight: 700; color: var(--dash-text-muted); margin-bottom: 8px; }
.dash-kpi-wert { font-family: 'Fredoka', sans-serif; font-weight: 600; font-size: 32px; color: var(--dash-ink); font-variant-numeric: tabular-nums; }
.dash-kpi-wert.ok { color: var(--dash-ink); }
.dash-kpi-wert.info { color: var(--dash-ink-soft); }
.dash-kpi-wert.warn { color: var(--dash-warn); }
.dash-kpi-wert.crit { color: var(--dash-crit); }
/* Meter-Zeilen: Auslastung je Gewerk, Wochenbericht-Fortschritt je Gewerk.
Nicht mehr die Kachel als Ganzes ist antippbar, sondern jede einzelne
Gewerk-Zeile für sich (wandZeigeUebersicht('auslastung'|'besprochen', g)) —
deshalb trägt .ist-klickbar hier die Zeile, nicht mehr .wand-kachel. */
/* ===== Drei-Panel-Reihe: Auslastung / Besprochen / Aktuell =====
Dieselbe Kacheltechnik wie zuvor (space-evenly auf .dash-panel-body) —
der Inhalt verteilt sich gleichmäßig über die volle Höhe statt oben zu
kleben, damit ein dünn befülltes Panel neben einem vollen nicht leer
wirkt. */
.dash-grid { flex: 1; min-height: 0; display: grid; grid-template-columns: 1fr 1fr 1.3fr; gap: 14px; }
.dash-panel { border-radius: var(--r-xl); padding: 18px 20px; display: flex; flex-direction: column; min-height: 0; }
.dash-grid > .dash-panel:nth-child(1) { background: var(--dash-panel-1); }
.dash-grid > .dash-panel:nth-child(2) { background: var(--dash-panel-2); }
.dash-grid > .dash-panel:nth-child(3) { background: var(--dash-panel-3); }
.dash-panel-titel { flex-shrink: 0; font-size: 12px; font-weight: 700; color: var(--dash-ink); margin-bottom: 12px; }
.dash-panel-body { flex: 1; min-height: 0; display: flex; flex-direction: column; justify-content: space-evenly; overflow: hidden; }
/* Meter-Zeilen: Auslastung je Gewerk, Wochenbericht-Fortschritt je Gewerk —
jede Zeile einzeln antippbar (wandZeigeUebersicht('auslastung'|'besprochen', g)). */
.wand-meter-zeile { display: flex; align-items: center; gap: 12px; }
.wand-meter-zeile.ist-klickbar { cursor: pointer; border-radius: var(--r-md); margin: 0 -6px; padding: 3px 6px; transition: background .15s var(--ease); }
.wand-meter-zeile.ist-klickbar:hover { background: var(--surface-sunk); }
.wand-meter-label { flex: 0 0 90px; font-size: 13px; font-weight: 600; color: var(--text); }
.wand-meter-zeile.ist-klickbar:hover { background: var(--dash-hover); }
.wand-meter-label { flex: 0 0 90px; font-size: 13px; font-weight: 600; color: var(--dash-text); }
.wand-meter {
flex: 1;
height: 18px;
background: var(--surface-sunk);
border-radius: var(--r-pill);
overflow: hidden;
display: flex;
align-items: center;
flex: 1; height: 14px; background: rgba(127, 127, 127, .18); border-radius: var(--r-pill);
overflow: hidden; display: flex; align-items: center;
}
.wand-meter-fill { height: 100%; border-radius: var(--r-pill); transition: width .3s var(--ease); }
.wand-meter-leer { font-size: 12px; color: var(--text-subtle); padding-left: 4px; }
.wand-meter-leer { font-size: 12px; color: var(--dash-text-subtle); padding-left: 4px; }
.wand-meter-wert {
flex: 0 0 76px;
text-align: right;
font-size: 15px;
font-weight: 600;
font-variant-numeric: tabular-nums;
flex: 0 0 76px; text-align: right; font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums;
font-family: 'Fredoka', sans-serif; color: var(--dash-ink);
}
.wand-einheit { font-size: 11px; font-weight: 400; color: var(--text-subtle); margin-left: 2px; }
.wand-leer-klein { font-size: 11.5px; color: var(--text-subtle); }
.wand-einheit { font-size: 11px; font-weight: 400; color: var(--dash-text-subtle); margin-left: 2px; }
.wand-leer-klein { font-size: 11.5px; color: var(--dash-text-subtle); }
/* Kennzahlen: Kachel "Heute" (offene/überfällige Aufgaben, Abwesend) — jede
Kennzahl und jedes Kürzel ist für sich antippbar (.ist-klickbar), öffnet
die passende Vollbild-Übersicht. */
.wand-stat-reihe { display: flex; gap: 28px; }
.wand-stat { display: flex; flex-direction: column; gap: 2px; }
.wand-stat b { font-size: 30px; font-weight: 700; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.wand-stat span { font-size: 12px; color: var(--text-subtle); }
.wand-stat.ist-rot b { color: var(--red); }
.wand-stat.ist-klickbar { cursor: pointer; }
.wand-stat.ist-klickbar:hover b { text-decoration: underline; }
.wand-namen { display: flex; flex-wrap: wrap; gap: 6px; }
.wand-avatar {
width: 28px;
height: 28px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 10.5px;
font-weight: 700;
background: var(--accent-soft);
color: var(--accent);
}
.wand-avatar.ist-klickbar { cursor: pointer; }
.wand-avatar.ist-klickbar:hover { box-shadow: 0 0 0 2px var(--accent); }
.wand-leer { font-size: 13px; color: var(--text-subtle); }
/* Ereignis-Feed (rechte Spalte) — auf Eriks Wahl "Variante C" aus dem
Design-Vergleich: drei Spalten nach Art (Neu/Überfällig/Termine) statt
einer gemischten, chronologisch sortierten Liste. Zeigt auf einen Blick,
WIE VIELE Ereignisse welcher Art anstehen — kostet dafür die vorherige
"zeitlich am nächsten zuerst"-Durchmischung über die Arten hinweg;
innerhalb einer Spalte sortiert wandAktuelleEreignisse() unverändert nach
zeitlicher Nähe. Jeder Eintrag bleibt für sich antippbar und springt
direkt ins betroffene Projekt. */
.wand-feed-wrap { flex: 1; min-height: 0; overflow: hidden; }
.wand-feed-gruppen { display: flex; gap: 16px; height: 100%; }
.wand-feed-gruppe { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; overflow: hidden; }
.wand-feed-gruppe-kopf { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; flex-shrink: 0; }
.wand-feed-gruppe-punkt { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.wand-feed-gruppe-punkt.neu { background: var(--accent); }
.wand-feed-gruppe-punkt.ueberfaellig { background: var(--red); }
.wand-feed-gruppe-punkt.termin { background: var(--amber); }
.wand-feed-gruppe-titel {
font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted);
}
.wand-feed-gruppe-anzahl { font-size: 10.5px; color: var(--text-subtle); margin-left: auto; }
.wand-feed-eintrag {
font-size: 12px; line-height: 1.4; padding: 8px 0; border-top: 1px solid var(--line-soft);
/* "Aktuell": flache, chronologisch sortierte Ereignisliste (wandAktuellInhalt(),
js/wand/wand.js) — ein farbiger Punkt statt einer Pille zeigt die Art
(Neu/Überfällig/Termin). Jeder Eintrag einzeln antippbar, springt direkt
ins betroffene Projekt. */
.dash-feed-eintrag {
display: flex; align-items: flex-start; gap: 10px; padding: 9px 2px; border-top: 1px solid var(--dash-line);
cursor: pointer; border-radius: var(--r-md); transition: background .15s var(--ease);
}
.wand-feed-eintrag:first-of-type { border-top: none; padding-top: 0; }
.wand-feed-eintrag:hover { background: var(--surface-sunk); }
.wand-feed-eintrag b { display: block; font-weight: 600; color: var(--text); margin-bottom: 1px; }
.wand-feed-eintrag span { color: var(--text-muted); }
.wand-feed-zeit { display: block; font-size: 10.5px; color: var(--text-subtle); margin-top: 2px; }
.wand-stand { text-align: center; font-size: 11px; color: var(--text-subtle); margin-top: 10px; flex-shrink: 0; }
.dash-feed-eintrag:first-of-type { border-top: none; padding-top: 0; }
.dash-feed-eintrag:hover { background: var(--dash-hover); }
.dash-feed-punkt { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; margin-top: 6px; }
.dash-feed-punkt.neu { background: var(--dash-ink); }
.dash-feed-punkt.ueberfaellig { background: var(--dash-crit); }
.dash-feed-punkt.termin { background: var(--dash-warn); }
.dash-feed-text { flex: 1; font-size: 12.5px; line-height: 1.5; color: var(--dash-text); }
.dash-feed-zeit { display: block; font-size: 10.5px; color: var(--dash-text-subtle); margin-top: 2px; }
/* Vollbild-Übersichten (überfällige/offene Aufgaben, Auslastung/Besprochen
je Gewerk, Abwesenheiten) — ersetzen das zweispaltige Layout vorübergehend
je Gewerk, Abwesenheiten) — ersetzen die Normalansicht vorübergehend
vollständig (wandUebersicht). Die Liste ist die EINE bewusste Ausnahme vom
"kein Scrollen"-Grundsatz der Wandtafel: ein gezielter Einstieg mit
potenziell vielen Zeilen, kein passiver Dauerzustand. */
potenziell vielen Zeilen, kein passiver Dauerzustand. In derselben
Logo-Farbwelt wie die Normalansicht, damit der Wechsel nicht wie ein
Bruch in eine andere App wirkt. */
.wand-uebersicht {
flex: 1; min-height: 0; display: flex; flex-direction: column;
background: var(--surface); border: 1px solid var(--line-soft); border-radius: var(--r-xl);
box-shadow: var(--sh-2); padding: 20px 24px;
background: var(--dash-surface); border: 1px solid var(--dash-line); border-radius: var(--r-xl);
box-shadow: var(--sh-2); padding: 20px 24px; color: var(--dash-text);
}
.wand-uebersicht-kopf { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; flex-shrink: 0; }
.wand-uebersicht-kopf h2 { margin: 0; font-size: 19px; font-weight: 700; letter-spacing: -.01em; }
.wand-uebersicht-kopf h2 { margin: 0; font-size: 19px; font-weight: 700; letter-spacing: -.01em; font-family: 'Fredoka', sans-serif; }
.wand-uebersicht-liste { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; }
.wand-uebersicht-zeile {
display: flex; align-items: center; gap: 18px; padding: 13px 6px; border-top: 1px solid var(--line-soft);
display: flex; align-items: center; gap: 18px; padding: 13px 6px; border-top: 1px solid var(--dash-line);
transition: background .15s var(--ease);
}
.wand-uebersicht-zeile:first-child { border-top: none; }
.wand-uebersicht-zeile.ist-klickbar { cursor: pointer; }
.wand-uebersicht-zeile.ist-klickbar:hover { background: var(--surface-sunk); }
.wand-uebersicht-projekt { flex: 0 0 280px; font-weight: 600; font-size: 14.5px; color: var(--text); }
.wand-uebersicht-titel { flex: 1; font-size: 14.5px; color: var(--text); }
/* Neutral, nicht mehr fest rot: eine Übersichts-Zeile kann auch eine
unauffällige Angabe zeigen (Stunden, "noch offen", Abwesenheitsende) —
.ist-rot markiert gezielt die wirklich überfälligen Zeilen. */
.wand-uebersicht-seit { flex-shrink: 0; font-size: 13px; font-weight: 600; color: var(--text-muted); }
.wand-uebersicht-seit.ist-rot { color: var(--red); }
.wand-uebersicht-zeile.ist-klickbar:hover { background: var(--dash-hover); }
.wand-uebersicht-projekt { flex: 0 0 280px; font-weight: 600; font-size: 14.5px; color: var(--dash-text); }
.wand-uebersicht-titel { flex: 1; font-size: 14.5px; color: var(--dash-text); }
/* Neutral, nicht fest rot: eine Übersichts-Zeile kann auch eine unauffällige
Angabe zeigen (Stunden, "noch offen", Abwesenheitsende) — .ist-rot
markiert gezielt die wirklich überfälligen Zeilen. */
.wand-uebersicht-seit { flex-shrink: 0; font-size: 13px; font-weight: 600; color: var(--dash-text-muted); }
.wand-uebersicht-seit.ist-rot { color: var(--dash-crit); }
.wand-leer { font-size: 13px; color: var(--dash-text-subtle); }
/* Vollständige Projektliste unterhalb der zwei Spalten (wandProjektlisteHtml,
js/wand/wand.js) — nur sichtbar/erreichbar durch Herunterscrollen auf
/* Vollständige Projektliste unterhalb der Kennzahlen-/Panel-Reihe
(wandProjektlisteHtml(), js/wand/wand.js) — nur sichtbar/erreichbar durch
Herunterscrollen auf
einem echten Touch-Monitor. Auf Eriks Wunsch optisch identisch mit der
echten Projektliste: Gruppenüberschrift und Zeilen sind exakt
.section-title/.board-list/.board-row aus der Projektliste selbst (keine
@@ -1437,36 +1449,89 @@ tr:nth-child(even) td { background: var(--surface-alt); }
}
/* === Dichte Fassung der Karte (.board-drawer-dicht, Wochenbericht) ===
Gleiche Angaben, gleiche Bedienelemente, enger gesetzt. Den Ausschlag gibt
die Leistungsphasen-Leiste: als Zeilen untereinander bestimmten bis zu zehn
Phasen die Höhe der ganzen Karte — bei 29 Projekten wurde der Bericht damit
rund 17 Bildschirmseiten lang. Nebeneinander braucht dieselbe Information
eine Zeile. */
.board-drawer-dicht .board-drawer-grid {
/* Die Beteiligten brauchen so viel Breite wie der Kommentar: Sobald ihre
Kürzel umbrechen, bestimmt ihre Spalte die Höhe der ganzen Karte. Mit
1.45/1.15/1 waren es 14,2 Bildschirmseiten, mit gleich breiter
Beteiligten-Spalte 11,6 — mehr Breite bringt darüber hinaus nichts,
weil dann ohnehin nichts mehr umbricht. */
/* Die rechte Spalte bekommt eine MINDESTBREITE: Zehn Phasenfelder in einer
reinen fr-Spalte wurden 27 px schmal, und "100" passte nicht mehr ins
Eingabefeld — 79 Werte waren abgeschnitten. Unter 620 px Containerbreite
gibt die Mindestbreite nach und die Spalten rutschen untereinander. */
grid-template-columns: minmax(0, 1fr) minmax(340px, 1fr);
grid-template-areas: "kommentar kompakt";
gap: 18px;
Fokus-Modus (Eriks Wahl "C" aus dem Darstellungs-Vergleich): nur der
Kommentar liegt offen, Leistungsphasen und Beteiligte stecken in
aufklappbaren Abschnitten (.bericht-disclosure, siehe renderBoardDrawer()s
dichte Fassung in js/board/board.js) statt dauerhaft nebeneinander zu
stehen — bei 29 Projekten wurde der Bericht mit allem gleichzeitig
sichtbar als "zu überladen" zurückgemeldet. Kein eigenes Raster mehr (die
drei Blöcke stehen schlicht untereinander), deshalb sitzt die dichte
Fassung flächig in ihrem Container statt als eigene Schublade mit
zusätzlichem Rahmen/Hintergrund darin. */
.board-drawer-dicht { padding: 0; background: transparent; border-top: none; }
.bericht-disclosure {
border: 1px solid var(--line-soft);
border-radius: var(--r-md);
margin-top: 10px;
overflow: hidden;
background: var(--surface);
}
@media (max-width: 860px) {
.board-drawer-dicht .board-drawer-grid {
grid-template-columns: 1fr;
grid-template-areas: "kommentar" "kompakt";
gap: 14px;
}
.bericht-disclosure summary {
list-style: none;
cursor: pointer;
padding: 10px 14px;
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
font-weight: 650;
color: var(--text-muted);
}
.drawer-col-kompakt { grid-area: kompakt; min-width: 0; }
/* Die zweite Überschrift in derselben Spalte braucht Abstand nach oben, den
die erste (am Spaltenanfang) nicht haben darf. */
.drawer-h4-zweit { margin-top: 12px; }
.bericht-disclosure summary::-webkit-details-marker { display: none; }
.bericht-disclosure summary:hover { background: var(--surface-alt); }
.bericht-disclosure summary .count {
font-size: 11px;
color: var(--text-subtle);
font-weight: 500;
margin-left: 2px;
}
.bericht-disclosure summary .chev {
margin-left: auto;
color: var(--text-subtle);
display: inline-flex;
transition: transform .15s var(--ease);
}
.bericht-disclosure[open] summary .chev { transform: rotate(90deg); }
.bericht-disclosure[open] summary { border-bottom: 1px solid var(--line-soft); }
.bericht-disclosure-body { padding: 10px 14px 12px; }
/* Leistungsphasen als Balkenzeilen statt der Kästchen-Leiste des
Projektboards (.board-lph-leiste/-cell bleiben dort unverändert) — in der
schmalen Fokus-Spalte brauchen zehn Kästchen nebeneinander zu wenig Platz
je Phase, eine Zeile je Phase dagegen hat Raum für Label, Balken UND
Eingabefeld. */
.bericht-lph-row { display: flex; align-items: center; gap: 10px; padding: 5px 0; font-size: 12.5px; }
.bericht-lph-label { width: 48px; flex-shrink: 0; color: var(--text-muted); }
.bericht-lph-track { flex: 1; height: 4px; background: var(--line-soft); border-radius: 2px; overflow: hidden; }
.bericht-lph-track i { display: block; height: 100%; background: var(--text-subtle); }
.bericht-lph-row.is-aktiv .bericht-lph-track i { background: var(--accent); }
.bericht-lph-row.is-aktiv .bericht-lph-label { color: var(--accent); font-weight: 650; }
.bericht-lph-pct {
display: flex; align-items: center; gap: 1px; flex-shrink: 0;
color: var(--text-subtle); font-variant-numeric: tabular-nums; font-size: 11.5px;
}
/* .bericht-lph-row (zwei Klassen) statt der bloßen Element-Klasse: nötig, um
den globalen input[type=text]-Grundstil (Polsterung/Rahmen/Hintergrund,
weiter oben) zu schlagen — sonst gewinnt dessen höhere Spezifität und die
Polsterung von 7px 10px ließ "100" im 42px schmalen Feld auf "10"
abschneiden. */
.bericht-lph-row .bericht-lph-input {
width: 28px; flex-shrink: 0; border: none; border-radius: 0; background: transparent;
text-align: right; font-size: 11.5px; font-variant-numeric: tabular-nums; color: var(--text);
font-family: inherit; padding: 1px 0;
}
.bericht-lph-row .bericht-lph-input:focus { outline: 1.5px solid var(--accent); outline-offset: -1px; border-radius: 3px; box-shadow: none; }
.bericht-lph-row .bericht-lph-input.locked-cell { cursor: default; }
/* Der Kommentar bleibt das größte Element der dichten Fassung — etwas
größere Schrift als im Projektboard, wo er eine Spalte unter mehreren
teilt. Radius/Schatten wie eine freistehende Karte (Entwurf "Fokus-Modus"). */
.board-drawer-dicht .comment-editor .comment-field,
.board-drawer-dicht .comment-readonly { font-size: 14px; line-height: 1.6; min-height: 76px; }
/* .comment-editor hat bereits overflow:hidden (Basis-Regel) — ein größerer
Radius hier genügt, Kopf/Fuß werden automatisch mitgerundet. */
.board-drawer-dicht .comment-editor { border-radius: var(--r-lg); box-shadow: var(--sh-1); }
.board-lph-leiste {
display: flex;
@@ -2302,36 +2367,14 @@ a.secondary:hover { background: var(--surface-sunk); border-color: var(--text-su
}
/* === Anwesenheitsblock der Sitzung ======================================
Eigener Block oben im Projektboard (renderAttendance(), js/board/board.js).
Zustände sind bewusst binär (anwesend / nicht anwesend, Tabelle attendance);
der Zähler in der Kopfzeile macht sichtbar, wenn noch niemand geklickt hat. */
.attendance-block {
background: var(--surface);
border: 1px solid var(--line-soft);
border-radius: var(--r-lg);
box-shadow: var(--sh-1);
margin-top: 16px;
overflow: hidden;
}
.attendance-bar {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
padding: 9px 14px;
background: var(--surface-alt);
border-bottom: 1px solid var(--line-soft);
}
.attendance-title { font-weight: 600; font-size: 13px; }
.attendance-count {
font-size: 11.5px;
color: var(--text-muted);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.attendance-spacer { margin-left: auto; }
/* === Anwesenheit der Sitzung ============================================
Fokus-Modus (Eriks Wahl "C"): ein aufklappbarer Abschnitt wie Leistungs-
phasen/Beteiligte im Wochenbericht (.bericht-disclosure, siehe weiter
oben), kein dauerhaft sichtbarer Block mehr. Zustände bleiben binär
(anwesend / nicht anwesend, Tabelle attendance); der Zähler in der
Kopfzeile macht sichtbar, wenn noch niemand geklickt hat. */
#weekAttendance .bericht-disclosure { margin-top: 16px; }
.attendance-actions { display: flex; gap: 8px; margin-bottom: 10px; }
.attendance-grid {
display: flex;
@@ -2485,34 +2528,46 @@ input[type=text].edit-name:focus {
html:not([data-theme="light"]) { color-scheme: dark; }
html:not([data-theme="light"]) {
--accent: #0a84ff;
--accent-hover: #2f95ff;
--accent-active: #0060df;
--accent-soft: #10263f;
/* Auf Eriks Wunsch ("das gesamte dark mode Farbschema an das der
Wandtafel anpassen") übernimmt der GESAMTE App-Dark-Mode die
Logo-Blau-Familie des Dashboards (--dash-* in body.wandtafel-aktiv
weiter oben) statt des vorherigen, generischen Apple-Blaus — alle
Komponenten der App lesen ausschließlich über diese Tokens, eine
Änderung hier genügt also für die ganze Anwendung. Rot/Gelb/Grün
bleiben als eigenständige Statusfarben bestehen (müssen unabhängig
von der Markenfarbe sofort erkennbar sein, siehe Ampel-Logik in
js/load/load.js), nur in denselben gedeckteren Tönen wie am Dashboard. */
--accent: #7fc3dd;
--accent-hover: #96cfe3;
--accent-active: #5fa3c0;
--accent-soft: #16222c;
--text: #f5f5f7;
--text-muted: #a1a1a6;
--text-subtle: #86868b;
--text: #e7eef3;
--text-muted: #9fb3c0;
--text-subtle: #70838f;
--bg: #000000;
--surface: #1c1c1e;
--surface-alt: #232326;
--surface-sunk: #2c2c2e;
--bg: #10181f;
--surface: #141c23;
--surface-alt: #16222c;
--surface-sunk: #1c2730;
--line: #3a3a3c;
--line-soft: #2c2c2e;
--line: #2c3e49;
--line-soft: #1f2b33;
--green: #30d158;
--amber: #ff9f0a;
--red: #ff453a;
--red-hover: #ff453a;
--red-active: #a3000f;
--green: #4cd080;
--amber: #e8a15c;
--red: #ef6f63;
--red-hover: #f58a7f;
--red-active: #c65349;
--attend-bg: rgba(48,209,88,.16);
--attend-bg-hover: rgba(48,209,88,.24);
--attend-bg: rgba(76,208,128,.14);
--attend-bg-hover: rgba(76,208,128,.22);
--band-neu: #0a6fd0;
--band-fortschritt: #1e7a38;
/* Auf Weiß gesetzt — deshalb dunkler als --band-neu/--green gebraucht
(Kontrastgrund wie im Hell-Modus): --band-neu ist das dunkle
Logo-Blau selbst (dasselbe #116896 wie am Dashboard). */
--band-neu: #116896;
--band-fortschritt: #1f7a4f;
--protokoll-neu-bg: rgba(255,214,10,.14);
--protokoll-neu-border: #ffd60a;
@@ -2526,34 +2581,34 @@ input[type=text].edit-name:focus {
html[data-theme="dark"] {
color-scheme: dark;
--accent: #0a84ff;
--accent-hover: #2f95ff;
--accent-active: #0060df;
--accent-soft: #10263f;
--accent: #7fc3dd;
--accent-hover: #96cfe3;
--accent-active: #5fa3c0;
--accent-soft: #16222c;
--text: #f5f5f7;
--text-muted: #a1a1a6;
--text-subtle: #86868b;
--text: #e7eef3;
--text-muted: #9fb3c0;
--text-subtle: #70838f;
--bg: #000000;
--surface: #1c1c1e;
--surface-alt: #232326;
--surface-sunk: #2c2c2e;
--bg: #10181f;
--surface: #141c23;
--surface-alt: #16222c;
--surface-sunk: #1c2730;
--line: #3a3a3c;
--line-soft: #2c2c2e;
--line: #2c3e49;
--line-soft: #1f2b33;
--green: #30d158;
--amber: #ff9f0a;
--red: #ff453a;
--red-hover: #ff453a;
--red-active: #a3000f;
--green: #4cd080;
--amber: #e8a15c;
--red: #ef6f63;
--red-hover: #f58a7f;
--red-active: #c65349;
--attend-bg: rgba(48,209,88,.16);
--attend-bg-hover: rgba(48,209,88,.24);
--attend-bg: rgba(76,208,128,.14);
--attend-bg-hover: rgba(76,208,128,.22);
--band-neu: #0a6fd0;
--band-fortschritt: #1e7a38;
--band-neu: #116896;
--band-fortschritt: #1f7a4f;
--protokoll-neu-bg: rgba(255,214,10,.14);
--protokoll-neu-border: #ffd60a;
@@ -3862,6 +3917,10 @@ html[data-theme="dark"] .absence-type-fortbildung { background: rgba(255,159,10,
.ai-url-panel.is-open { display: block; }
.ai-url-panel label { display: block; font-size: 11px; color: var(--text-subtle); margin-bottom: 5px; }
.ai-url-panel .ai-provider-row { margin-bottom: 0; }
/* Zweites Label-/Zeilenpaar im selben Panel (Modellname, unter der
Server-Adresse): braucht Abstand nach oben, den das erste (am
Panel-Anfang) nicht haben darf — gleiches Muster wie .drawer-h4-zweit. */
.ai-url-panel label:nth-of-type(2) { margin-top: 12px; }
/* "Neu verbinden": prüft die gespeicherte Konfiguration wirklich (POST
/my-ai-settings/test), statt nur anzuzeigen, dass irgendein Schlüssel
@@ -4115,19 +4174,62 @@ td.actions-cell button { margin: 2px; }
color: var(--text-muted);
}
/* === Wochenbericht als Tagesordnung (js/wochenbericht/wochenbericht.js) ===
Links die vollständige Projektliste, rechts das gewählte Projekt. Die
frühere Abfolge gestapelter Karten war bei 29 Projekten 18 Bildschirmseiten
lang; hier bleibt die Liste stehen und man sieht jederzeit, wo man steht
und was noch kommt. */
/* === Wochenbericht als Tagesordnung, Fokus-Modus (Eriks Wahl "C" aus dem
Darstellungs-Vergleich, js/wochenbericht/wochenbericht.js) ===
Links eine schmale Nummern-Leiste (jedes Projekt nur eine Position + ggf.
ein Änderungs-Punkt, Name im Tooltip), rechts das gewählte Projekt — nur
Kommentar und Kernstatus liegen dort offen, Leistungsphasen/Beteiligte/
Anwesenheit stecken in aufklappbaren Abschnitten (.bericht-disclosure).
Ersetzt die vorherige ausgeschriebene Liste|Detail-Darstellung, die mit
Gewerke-Dropdown, Fortschrittsbalken, Suchfeld, mehreren Badges je Zeile
und allem gleichzeitig sichtbar als "zu überladen" zurückgemeldet wurde.
Eine ganze Fläche (.bericht-panel), nicht zwei lose nebeneinanderstehende
Karten — genau wie im freigegebenen Entwurf: eine Kopfleiste mit eigenem
Hintergrund/Trennlinie, darunter die Leiste als Spalte MIT dem Ergebnis
verbunden (Trennlinie statt eigenem Rahmen). */
.bericht-titelzeile { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
.bericht-titelzeile button { margin-left: auto; }
.bericht-kopf {
display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
margin-bottom: 12px;
.bericht-panel {
background: var(--surface); border: 1px solid var(--line-soft);
border-radius: var(--r-lg); box-shadow: var(--sh-1); overflow: hidden;
}
.bericht-gewerk-select { font-size: 12.5px; padding: 5px 10px; min-width: 170px; }
.bericht-topbar {
display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
padding: 10px 20px; border-bottom: 1px solid var(--line-soft);
}
.bericht-gewerk-select {
font-size: 12px; font-weight: 600; padding: 5px 10px;
border-radius: var(--r-sm); background: var(--surface-alt);
border: 1px solid var(--line); color: var(--text); cursor: pointer;
}
.bericht-ring {
width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0;
display: flex; align-items: center; justify-content: center;
}
.bericht-ring span { width: 18px; height: 18px; border-radius: 50%; background: var(--surface); display: block; }
.bericht-fortschritt-text { font-size: 11.5px; color: var(--text-muted); font-weight: 600; white-space: nowrap; }
/* Reines Textlink, kein Knopf — im Entwurf steht an dieser Stelle nur der
Fortschritt, nicht noch eine zusätzliche Schaltfläche daneben. */
.bericht-reset-link {
font-size: 11.5px; font-weight: 600; border: none; background: none;
color: var(--text-subtle); cursor: pointer; padding: 0; text-decoration: underline;
text-decoration-color: var(--line);
}
.bericht-reset-link:hover { color: var(--text); }
.bericht-search-btn {
margin-left: auto; border: 1px solid var(--line); background: var(--surface-alt);
color: var(--text-subtle); font-size: 12px; padding: 5px 10px;
border-radius: var(--r-pill); cursor: pointer;
}
.bericht-search-btn:hover { color: var(--text); border-color: var(--text-subtle); }
.bericht-suche-input {
margin-left: auto; border: 1px solid var(--line); border-radius: var(--r-pill);
background: var(--surface-sunk); color: var(--text); font-size: 12px;
padding: 6px 12px; width: 170px;
}
.bericht-suche-input:focus { outline: none; border-color: var(--accent); background: var(--surface); }
/* ---------- Planung (Terminplan-Sandbox, js/planung/planung.js) ---------- */
.planung-row {
@@ -4161,118 +4263,168 @@ td.actions-cell button { margin: 2px; }
.choice-card-desc { font-size: 11.5px; color: var(--text-muted); line-height: 1.4; }
.empty-hint { text-align: center; padding: 60px 20px; color: var(--text-subtle); font-size: 13px; }
.empty-hint .big { font-size: 15px; color: var(--text-muted); margin-bottom: 6px; display: block; }
.bericht-fortschritt { display: flex; align-items: center; gap: 9px; margin-left: auto; }
.bericht-fortschritt-text { font-size: 12.5px; color: var(--text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.bericht-fortschritt-bar { width: 120px; height: 6px; border-radius: 3px; background: var(--surface-sunk); overflow: hidden; }
.bericht-fortschritt-bar i { display: block; height: 100%; background: var(--green); transition: width .25s var(--ease); }
.bericht-split {
display: grid;
grid-template-columns: 272px minmax(0, 1fr);
gap: 16px;
align-items: start;
/* ---------- Plan Checker (Studenten, js/planchecker/planchecker.js) ----------
Darstellung "Arbeitsfläche" (Eriks Wahl "B" aus dem Design-Vergleich): zwei
Spalten dauerhaft nebeneinander — links Upload/Konfiguration, rechts das
Ergebnis, damit ein erneutes Prüfen nach einer Korrektur das vorherige
Ergebnis nicht aus dem Blick verliert. Eigene .pc-*-Klassen statt
bestehender Komponenten (.board-drawer, .settings-card …): keine davon
passt auf eine Zwei-Spalten-Arbeitsfläche mit Ablagefläche + Ergebnisliste,
ein Zurechtbiegen hätte mehr Sonderfälle erzeugt als eine schlanke eigene
Regelgruppe. */
.pc-layout { display: grid; grid-template-columns: 340px 1fr; gap: 20px; align-items: start; }
@media (max-width: 860px) { .pc-layout { grid-template-columns: 1fr; } }
.pc-panel { background: var(--surface); border: 1px solid var(--line-soft); border-radius: var(--r-lg); box-shadow: var(--sh-1); padding: 20px; }
.pc-drop {
display: block; border: 1.5px dashed var(--line); border-radius: var(--r-md);
padding: 20px 14px; text-align: center; color: var(--text-muted);
background: var(--surface-alt); cursor: pointer; font-size: 12.5px;
transition: border-color .15s var(--ease), color .15s var(--ease), background .15s var(--ease);
}
@media (max-width: 900px) { .bericht-split { grid-template-columns: 1fr; } }
.pc-drop:hover, .pc-drop.is-dragover { border-color: var(--accent); color: var(--text); background: var(--accent-soft); }
.pc-drop-name { font-weight: 650; color: var(--text); margin-top: 4px; font-size: 13px; word-break: break-word; }
.bericht-liste-spalte {
background: var(--surface); border-radius: var(--r-md); box-shadow: var(--sh-1);
overflow: hidden;
/* Die Liste bleibt beim Scrollen im Blick — sie IST die Tagesordnung. */
.pc-section { margin-top: 18px; }
.pc-label { display: block; font-size: 11.5px; font-weight: 650; color: var(--text-muted); margin-bottom: 5px; }
.pc-merkmal-chip { font-size: 12.5px; padding: 5px 2px; color: var(--text-muted); }
.pc-zusatz { margin-top: 8px; }
.pc-zusatz .pc-input { width: 100%; }
/* background-color statt der background-Kurzschreibweise: Die Kurzschreibweise
setzt background-repeat/-position implizit auf ihre Vorgabewerte zurück
(repeat/0% 0%) und schlägt dabei wegen höherer Klassen-Spezifität die
globalen select-Regeln (background-repeat:no-repeat, siehe weiter oben),
die den eigenen SVG-Pfeil sonst nur einmal rechts zeigen — das gekachelte
Pfeil-Muster, das als "komisch" auffiel, war genau das. */
.pc-select, .pc-input {
border: 1px solid var(--line); border-radius: var(--r-md); background-color: var(--surface);
color: var(--text); font-size: 13px; font-family: inherit; padding: 8px 10px;
}
.pc-select:focus, .pc-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }
.pc-btn {
border: 1px solid var(--line); background: var(--surface); color: var(--text);
font-size: 13px; font-weight: 600; padding: 9px 16px; border-radius: var(--r-md); cursor: pointer;
}
.pc-btn:hover { background: var(--surface-alt); }
.pc-btn:disabled { opacity: .6; cursor: default; }
.pc-btn-primary { border: none; background: var(--accent); color: #fff; }
.pc-btn-primary:hover { background: var(--accent-hover); }
.pc-error { color: var(--red); margin: 8px 0 0; }
.pc-verdict {
display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
width: 20px; height: 20px; border-radius: 50%; font-size: 12px; font-weight: 700; color: #fff;
}
.pc-verdict.ok { background: var(--green); }
.pc-verdict.fail { background: var(--red); }
.pc-results { min-height: 420px; display: flex; flex-direction: column; }
.pc-results-empty { margin: auto; text-align: center; color: var(--text-subtle); padding: 40px 20px; }
.pc-results-empty .big { font-size: 14px; color: var(--text-muted); margin-bottom: 4px; display: block; }
.pc-result-head { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; padding-bottom: 14px; border-bottom: 1px solid var(--line-soft); flex-wrap: wrap; }
.pc-result-head .filename { font-weight: 650; }
.pc-result-head .meta { color: var(--text-subtle); font-size: 12px; margin-left: auto; }
.pc-result-row { display: flex; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--line-soft); }
.pc-result-row:last-child { border-bottom: none; }
.pc-result-row .titel { font-weight: 650; margin-bottom: 2px; font-size: 13px; }
.pc-result-row .begruendung { color: var(--text-muted); font-size: 12.5px; }
.pc-result-text { flex: 1 1 auto; min-width: 0; }
/* pc-result-shot: Screenshot des Planausschnitts, den die KI für das Merkmal
als Beleg gewählt hat (server.js, pruefePlanMitKI(), bildIndex) — nur
vorhanden, wenn die KI einen gültigen Bild-Index geliefert hat. Ein
<button>, kein <a> (öffnet #pcShotModal statt eines Links auf die
data:-URL, s. planCheckerZeigeScreenshot() — ein Link blieb wirkungslos,
da Chrome das Öffnen einer data:-URL in einem neuen Tab blockiert),
deshalb die Button-Vorgaben zurückgesetzt. */
.pc-result-shot { flex: 0 0 auto; display: block; background: none; border: none; padding: 0; cursor: pointer; }
.pc-result-shot img {
width: 110px; height: 80px; object-fit: cover; display: block;
border: 1px solid var(--line-soft); border-radius: var(--r-sm); background: var(--surface-alt);
}
.pc-result-shot:hover img { border-color: var(--accent); }
.bericht-layout { display: grid; grid-template-columns: 64px minmax(0, 1fr); }
@media (max-width: 760px) { .bericht-layout { grid-template-columns: 48px minmax(0, 1fr); } }
.bericht-rail {
background: var(--surface-alt);
border-right: 1px solid var(--line-soft);
padding: 10px 0; display: flex; flex-direction: column; align-items: center; gap: 3px;
/* Die Leiste bleibt beim Scrollen im Blick — sie IST die Tagesordnung. Kein
eigener Rahmen/Radius/Schatten mehr und keine eigene Höhenbegrenzung:
die Fläche gehört zu .bericht-panel, und eine gedeckelte Höhe zwang den
eigenen (inzwischen entfernten) Tooltip zum Abschneiden — ein Element mit
overflow-y ungleich visible schneidet nach der CSS-Spezifikation auch in
X-Richtung ab. */
position: sticky; top: 64px;
}
.bericht-suche { padding: 8px 9px; border-bottom: 1px solid var(--line-soft); }
.bericht-suche input { width: 100%; padding: 5px 9px; font-size: 12.5px; }
.bericht-liste { max-height: min(62vh, 620px); overflow-y: auto; }
.bericht-rail-item {
position: relative; width: 40px; height: 40px; border-radius: 50%; flex-shrink: 0;
display: flex; align-items: center; justify-content: center;
color: var(--text-subtle); cursor: pointer;
}
.bericht-rail-item:hover { background: var(--surface-sunk); color: var(--text); }
.bericht-rail-item:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.bericht-rail-item.is-aktiv { background: var(--accent); color: #fff; }
/* Abgehakt: zurückgenommen, aber erkennbar — man muss im Meeting noch sehen
können, was man schon besprochen hat. */
.bericht-rail-item.is-ab { opacity: .55; }
.bericht-rail-item.is-aktiv.is-ab { opacity: 1; }
.bericht-rail-num { font-size: 10.5px; font-weight: 700; font-variant-numeric: tabular-nums; }
.bericht-rail-dot { position: absolute; top: 3px; right: 3px; width: 6px; height: 6px; border-radius: 50%; }
.bericht-rail-dot.is-new, .bericht-rail-dot.is-status { background: var(--band-neu); }
.bericht-rail-dot.is-progress { background: var(--band-fortschritt); }
.bericht-rail-item.is-aktiv .bericht-rail-dot { outline: 1.5px solid var(--accent-soft); outline-offset: -1px; }
.bericht-rail-tooltip {
position: absolute; left: 48px; top: 50%; transform: translateY(-50%);
background: var(--text); color: var(--surface); font-size: 11px; padding: 4px 8px;
border-radius: var(--r-sm); white-space: nowrap; opacity: 0; pointer-events: none;
transition: opacity .12s var(--ease); z-index: 5;
}
.bericht-rail-item:hover .bericht-rail-tooltip,
.bericht-rail-item:focus-visible .bericht-rail-tooltip { opacity: 1; }
.bericht-gruppe {
font-size: 10px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
color: var(--text-subtle); padding: 9px 11px 3px;
}
.bericht-zeile {
display: flex; align-items: center; gap: 7px;
padding: 5px 10px; cursor: pointer;
border-left: 2px solid transparent;
font-size: 12.5px;
}
.bericht-zeile:hover { background: var(--surface-alt); }
.bericht-zeile:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.bericht-zeile.is-aktiv { background: var(--accent-soft); border-left-color: var(--accent); }
/* Abgehakt: zurückgenommen, aber lesbar — man muss im Meeting noch erkennen
können, was man besprochen hat. */
.bericht-zeile.is-ab .bericht-zeile-name,
.bericht-zeile.is-ab .bericht-zeile-nr { color: var(--text-subtle); }
.bericht-zeile-nr {
font-family: ui-monospace, 'SF Mono', monospace; font-size: 11px;
color: var(--text-muted); flex-shrink: 0;
}
.bericht-zeile-name { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* max-width: die Hauptspalte bleibt eine schmale Lesespalte statt bis zum
rechten Rand zu laufen — Teil der Fokus-Idee, nicht nur die Nebeninfos
wegzuklappen, sondern auch dem Kommentar selbst keine unnötige Breite zu
geben. */
.bericht-main { padding: 26px 36px 30px; max-width: 640px; min-width: 0; }
.bericht-haken {
width: 17px; height: 17px; flex-shrink: 0; padding: 0;
display: inline-flex; align-items: center; justify-content: center;
border: 1.5px solid var(--line); border-radius: 50%;
background: transparent; color: transparent;
font-size: 10px; line-height: 1; cursor: pointer;
.bericht-head-line { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }
.bericht-head-nr {
font-family: ui-monospace, 'SF Mono', monospace; font-size: 11.5px;
color: var(--text-subtle);
}
.bericht-haken:hover:not(:disabled) { border-color: var(--green); color: var(--green); }
.bericht-haken.ab { background: var(--green); border-color: var(--green); color: #fff; }
.bericht-haken:disabled { cursor: default; opacity: .5; }
/* Änderungs-Kennzeichen: nur noch ein Punkt in der Liste — die Gruppierung
nach Änderungsart ist entfallen, weil im Meeting ohnehin jedes Projekt
besprochen wird. Die ausformulierte Begründung steht im Detail. */
/* Die Zustandsklassen tragen ein "is-"-Präfix: Ohne es hieß eine davon
schlicht "status" und traf die Regel .status { min-width: 240px } weiter
oben — der 7-px-Punkt wurde 240 px breit und quetschte den Projektnamen
auf null. */
.bericht-punkt { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.bericht-punkt.is-new, .bericht-punkt.is-status { background: var(--band-neu); }
.bericht-punkt.is-progress { background: var(--band-fortschritt); }
.bericht-abwesend {
font-size: 10px; font-weight: 600; color: var(--amber);
background: color-mix(in srgb, var(--amber) 15%, transparent);
border-radius: var(--r-pill); padding: 0 5px; flex-shrink: 0;
.bericht-change-pill {
font-size: 10.5px; font-weight: 700; color: #fff; padding: 2px 9px; border-radius: var(--r-pill);
}
.bericht-detail {
background: var(--surface); border-radius: var(--r-md); box-shadow: var(--sh-1);
padding: 15px 17px 16px; min-width: 0;
}
.bericht-detail-kopf { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.bericht-detail-kopf h3 { margin: 0; font-size: 16px; font-weight: 600; }
.bericht-detail-zaehler {
font-family: ui-monospace, 'SF Mono', monospace; font-size: 11px;
color: var(--text-subtle); font-variant-numeric: tabular-nums;
}
.bericht-detail-meta { color: var(--text-muted); font-size: 12px; margin: 2px 0 12px; }
.bericht-kennz {
font-size: 11px; border-radius: var(--r-pill); padding: 1px 9px; color: #fff;
}
.bericht-kennz.is-new, .bericht-kennz.is-status { background: var(--band-neu); }
.bericht-kennz.is-progress { background: var(--band-fortschritt); }
.bericht-change-pill.is-new, .bericht-change-pill.is-status { background: var(--band-neu); }
.bericht-change-pill.is-progress { background: var(--band-fortschritt); }
.bericht-h1 { font-size: 21px; font-weight: 650; letter-spacing: -0.012em; margin: 0 0 3px; }
.bericht-sub { color: var(--text-subtle); font-size: 12px; margin-bottom: 16px; }
.bericht-abwesend-hinweis {
font-size: 12.5px; color: var(--amber);
background: color-mix(in srgb, var(--amber) 10%, transparent);
border-radius: var(--r-sm); padding: 6px 10px; margin-bottom: 10px;
display: inline-flex; align-items: center; gap: 6px;
color: var(--amber); font-size: 12px; margin-bottom: 16px;
}
/* Der Kommentar der Vorwoche steht ÜBER dem Eingabefeld: "Was hatten wir
letzte Woche vereinbart?" ist die häufigste Frage im Meeting. */
.bericht-vorwoche {
display: flex; gap: 9px; align-items: baseline;
padding: 7px 10px; margin-bottom: 11px;
background: var(--surface-alt); border-left: 2px solid var(--line);
border-radius: 0 var(--r-sm) var(--r-sm) 0;
font-size: 12.5px;
background: var(--surface-sunk); border-radius: var(--r-md);
padding: 9px 12px; margin-bottom: 14px; font-size: 12px; color: var(--text-muted);
}
.bericht-vorwoche-label {
display: block; font-weight: 600; color: var(--text-subtle);
text-transform: uppercase; font-size: 10.5px; letter-spacing: .05em; margin-bottom: 3px;
}
.bericht-vorwoche-label { font-weight: 600; color: var(--text-muted); white-space: nowrap; flex-shrink: 0; }
.bericht-vorwoche-text { color: var(--text-muted); }
.bericht-detail-fuss {
display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line-soft);
}
.bericht-detail-fuss { display: flex; align-items: center; gap: 14px; margin-top: 20px; }
.bericht-fuss-spacer { flex: 1; }
.person-chip-label { display: inline-flex; align-items: center; gap: 5px; cursor: pointer; }