Beteiligten-Zuordnung verlagert: p.assign ist jetzt ein Stammdatum und nur noch in den Projekteinstellungen editierbar (planCycleAssign()). Wochenbericht und Projektboard zeigen sie nur noch an, mit Verweis dorthin (springZuEinstellungen()). cycleAssign() ist damit entfallen. Neu "TODOs aus Protokoll": ein externes Besprechungsprotokoll als PDF wird per KI eingelesen und daraus in einem zweiten KI-Schritt Aufgaben abgeleitet. Eigener Bereich in der Aufgaben-/GANTT-Leiste, bewusst unabhaengig von den gespeicherten Protokollen - nichts davon wird abgelegt. Drei Schritte in einem Dialog mit fester Fusszeile; die Vorschlagsliste als kompakte, aufklappbare Zeilen, weil ein echter Import leicht 20 Vorschlaege liefert. Sichtbarkeit je Aufgabe im GANTT: neue Spalte plan_todos.show_in_gantt (Vorgabe 1, idempotente Migration) und eine Checkbox im Aufgaben-Dialog. Eine terminierte Aufgabe kann damit gezielt aus dem Terminplan herausgehalten werden, bleibt sonst aber eine vollwertige Aufgabe. plan-build-gantt.js und plan-drag.js pruefen dieselbe Bedingung, damit Zeilenzahl und Umsortierung nicht auseinanderlaufen. KI-Timeout dritte Stufe: ein abweichender, aber entfernter Endpunkt (Drittanbieter-Proxy) bekommt 2 Minuten statt der 45 Sekunden der echten Anbieter-API. Gemessen: die Punkte-Erkennung eines realen Protokolls brauchte dort 39 Sekunden, lag also stets knapp unter dem alten Limit und brach bei jeder Schwankung ab. Bewegung: Hover- und Klick-Rueckmeldung als Token-Satz in :root, angewandt auf die anklickbaren Zeilen, Schalter und Dialoge. Die Akzentleiste ist ein inset-Schatten statt eines positionierten Elements, damit Drag-and-Drop unberuehrt bleibt; prefers-reduced-motion war bisher gar nicht vorhanden und kippt jetzt einfach die Tokens. Plan Checker: optionaler Projektabgleich des Plankopfs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5255 lines
171 KiB
CSS
5255 lines
171 KiB
CSS
/* === Design-Tokens === */
|
|
:root {
|
|
--accent: #0071e3;
|
|
--accent-hover: #0077ed;
|
|
--accent-active: #006ad1;
|
|
--accent-soft: #e8f1fd;
|
|
|
|
--text: #1d1d1f;
|
|
--text-muted: #6e6e73;
|
|
--text-subtle: #86868b;
|
|
|
|
--bg: #f5f5f7;
|
|
--surface: #ffffff;
|
|
--surface-alt: #fafafc;
|
|
--surface-sunk: #f0f0f2;
|
|
|
|
--line: #d2d2d7;
|
|
--line-soft: #e8e8ed;
|
|
|
|
--green: #248a3d;
|
|
--amber: #b25000;
|
|
--red: #d70015;
|
|
--red-hover: #e8112d;
|
|
--red-active: #b8000f;
|
|
|
|
/* Flächen des Anwesenheits-Kacheln im Zustand "anwesend" — eigene Tokens,
|
|
weil eine feste rgba-Grünfläche im Dark Mode nicht trägt. */
|
|
--attend-bg: rgba(36,138,61,.10);
|
|
--attend-bg-hover: rgba(36,138,61,.16);
|
|
|
|
/* Kopfband der Wochenbericht-Karten je Änderungsart. Das Band trägt weiße
|
|
Schrift, deshalb etwas dunkler als --green (#248a3d käme nur auf
|
|
Kontrast 4,4 : 1 und bliebe damit unter den 4,5 : 1 für normalen Text). */
|
|
--band-neu: #0071e3;
|
|
--band-fortschritt: #22823a;
|
|
|
|
/* Gelbe Markierung neu hinzugekommener Protokoll-Punkte — bewusst eigenes
|
|
Token statt --band-neu (dort blau, bedeutet "neu kommentiertes Projekt"
|
|
im Wochenbericht — eine andere Bedeutung, die hier kollidieren würde). */
|
|
--protokoll-neu-bg: #fff6db;
|
|
--protokoll-neu-border: #e6b800;
|
|
|
|
/* Die drei Zustände eines Protokoll-Punkts (protokoll_punkte.status) —
|
|
eigene Bezeichner statt direkter --amber/--green/--accent-Nutzung an
|
|
jeder Stelle, damit sich die Farbzuordnung an einer Stelle ändern
|
|
ließe, ohne jede Verwendung einzeln anzufassen. Referenzieren andere
|
|
Tokens per var(), lösen sich also auch im Dark Mode korrekt auf, ohne
|
|
dort erneut definiert zu werden. --status-info-bg ist die Fläche einer
|
|
kompletten Info-Zeile in der Protokoll-Tabelle (color-mix statt fester
|
|
rgba-Werte, damit die Deckkraft in beiden Themes gleich wirkt). */
|
|
--status-offen: var(--amber);
|
|
--status-erledigt: var(--green);
|
|
--status-info: var(--accent);
|
|
--status-info-bg: color-mix(in srgb, var(--accent) 8%, transparent);
|
|
|
|
--r-sm: 6px;
|
|
--r-md: 8px;
|
|
--r-lg: 12px;
|
|
--r-xl: 16px;
|
|
--r-pill: 980px;
|
|
|
|
--sh-1: 0 1px 2px rgba(0,0,0,.04), 0 1px 3px rgba(0,0,0,.06);
|
|
--sh-2: 0 2px 8px rgba(0,0,0,.06), 0 1px 3px rgba(0,0,0,.04);
|
|
--sh-3: 0 8px 28px rgba(0,0,0,.12), 0 2px 8px rgba(0,0,0,.06);
|
|
|
|
--ease: cubic-bezier(.4, 0, .2, 1);
|
|
|
|
/* === Bewegung (Stufe "Spürbar", Eriks Wahl aus dem Interaktions-
|
|
Vergleich) ============================================================
|
|
Die gesamte Rückmeldung der Oberfläche hängt an diesen fünf Werten.
|
|
Eine andere Stufe wäre ein Wechsel HIER, nicht in den Komponenten:
|
|
jede Regel weiter unten liest ausschliesslich über var().
|
|
|
|
--dur kurze Rückmeldung (Fläche, Farbe, Anheben, Druck)
|
|
--dur-lg grössere Zustandswechsel (Aufklappen, Dialog, Band)
|
|
--ease-out Ausschwingen: schnell los, weich aus. Bewusst anders als
|
|
das ältere --ease (symmetrisch), das für Bestandsregeln
|
|
unverändert bleibt.
|
|
--row-lift wie weit sich eine anklickbare Zeile beim Zeigen hebt
|
|
--press Stauchung beim Drücken (Knöpfe, Chips, Zeilen)
|
|
|
|
Grundregeln, die überall gelten: bewegt wird nur transform und
|
|
opacity (nie Höhe/Breite, das ruckelt), nichts läuft in Dauerschleife,
|
|
und jede Regung quittiert eine echte Handlung. Wer im Betriebssystem
|
|
"Bewegung reduzieren" gesetzt hat, bekommt am Dateiende alles auf
|
|
praktisch null gesetzt - dort genügt es, diese Variablen zu kippen. */
|
|
--dur: 150ms;
|
|
--dur-lg: 200ms;
|
|
--ease-out: cubic-bezier(.2, .8, .2, 1);
|
|
--row-lift: -1px;
|
|
--press: .97;
|
|
}
|
|
|
|
/* === Basis & Reset === */
|
|
* { box-sizing: border-box; }
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI',
|
|
Roboto, Helvetica, Arial, sans-serif;
|
|
margin: 0;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
font-size: 13px;
|
|
line-height: 1.45;
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
letter-spacing: -0.005em;
|
|
}
|
|
|
|
.container { padding: 20px 24px; }
|
|
|
|
/* === App-Shell: globales Menüband oben + linke Navigations-Sidebar +
|
|
Hauptbereich (OpenProject-Anordnung: ein durchgehendes globales Menüband
|
|
trägt Marke, aktuellen Kontext [Sitzung] und alle Aktionen/Konto rechts;
|
|
die Sidebar darunter ist reine Modul-Navigation, sonst nichts) === */
|
|
.app-shell { display: flex; flex-direction: column; min-height: 100vh; }
|
|
.app-body { display: flex; flex: 1; min-height: 0; }
|
|
.app-main { flex: 1; min-width: 0; }
|
|
|
|
/* Generisches Icon-System (js/icons.js): Icons skalieren wie ein Zeichen mit
|
|
der umgebenden Schriftgröße (1.1em), erben die Textfarbe über currentColor. */
|
|
.icon { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
|
.icon svg { width: 1.1em; height: 1.1em; display: block; }
|
|
|
|
/* === Globales Menüband (immer sichtbar, alle Breiten) === */
|
|
.app-topbar {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 140;
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
row-gap: 8px;
|
|
gap: 12px;
|
|
padding: 8px 20px;
|
|
background: rgba(255,255,255,.82);
|
|
-webkit-backdrop-filter: saturate(180%) blur(20px);
|
|
backdrop-filter: saturate(180%) blur(20px);
|
|
border-bottom: 1px solid var(--line-soft);
|
|
}
|
|
|
|
.mobile-nav-toggle { display: inline-flex; }
|
|
@media (min-width: 769px) { .app-topbar .mobile-nav-toggle { display: none; } }
|
|
|
|
.topbar-brand { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
|
|
.topbar-logo { height: 28px; width: auto; object-fit: contain; }
|
|
.topbar-title {
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
margin: 0;
|
|
letter-spacing: -0.01em;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
|
|
.app-topbar-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
margin-left: auto;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.toolbar-group { display: flex; align-items: center; gap: 4px; }
|
|
|
|
.toolbar-divider {
|
|
width: 1px;
|
|
height: 22px;
|
|
background: var(--line-soft);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.app-topbar-actions {
|
|
order: 4;
|
|
flex: 1 1 100%;
|
|
margin-left: 0;
|
|
overflow-x: auto;
|
|
padding-bottom: 2px;
|
|
}
|
|
}
|
|
|
|
.header-toggle {
|
|
position: relative;
|
|
z-index: 10;
|
|
padding: 6px 10px;
|
|
min-width: 36px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.theme-toggle-circle {
|
|
width: 34px;
|
|
height: 34px;
|
|
border-radius: 50%;
|
|
padding: 0;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: var(--surface-sunk);
|
|
border: 1px solid var(--line-soft);
|
|
color: var(--text);
|
|
cursor: pointer;
|
|
flex-shrink: 0;
|
|
transition: background .2s var(--ease), color .2s var(--ease), transform .1s var(--ease);
|
|
}
|
|
|
|
.theme-toggle-circle:hover { background: var(--line-soft); }
|
|
.theme-toggle-circle:active { transform: scale(.95); }
|
|
|
|
.icon-btn-danger:hover { background: var(--red); color: #fff; }
|
|
.icon-btn-danger:active { background: var(--red-active); }
|
|
|
|
/* Konto-Menü (Avatar-Knopf rechts im Menüband + Dropdown) */
|
|
.topbar-user { position: relative; }
|
|
|
|
.user-menu-dropdown {
|
|
position: absolute;
|
|
top: calc(100% + 8px);
|
|
right: 0;
|
|
min-width: 220px;
|
|
background: var(--surface);
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-lg);
|
|
box-shadow: var(--sh-3);
|
|
padding: 8px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
z-index: 150;
|
|
opacity: 0;
|
|
visibility: hidden;
|
|
transform: translateY(-4px);
|
|
transition: opacity .15s var(--ease), transform .15s var(--ease), visibility .15s var(--ease);
|
|
}
|
|
|
|
.user-menu-dropdown.open {
|
|
opacity: 1;
|
|
visibility: visible;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.user-menu-info {
|
|
padding: 8px 10px 10px;
|
|
margin-bottom: 2px;
|
|
border-bottom: 1px solid var(--line-soft);
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
/* .menu-item: generischer Zeilen-Knopf/-Link (Text + optionales Icon) —
|
|
genutzt im Konto-Dropdown (dort über .user-menu-dropdown volle Breite) UND
|
|
als Basis für die Sidebar-Unterpunkte (siehe .sidebar-subitem). */
|
|
a.menu-item,
|
|
button.menu-item {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
text-align: left;
|
|
background: transparent;
|
|
color: var(--text);
|
|
padding: 8px 10px;
|
|
border: none;
|
|
border-radius: var(--r-md);
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: background .15s var(--ease);
|
|
}
|
|
|
|
a.menu-item:hover,
|
|
button.menu-item:hover { background: var(--surface-sunk); }
|
|
|
|
.user-menu-dropdown a.menu-item,
|
|
.user-menu-dropdown button.menu-item { display: flex; width: 100%; }
|
|
|
|
/* === Seiten-Navigation (links, reine Modul-Navigation) === */
|
|
.side-menu {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
bottom: 0;
|
|
width: 260px;
|
|
max-width: 80vw;
|
|
background: var(--surface);
|
|
border-right: 1px solid var(--line-soft);
|
|
box-shadow: var(--sh-3);
|
|
z-index: 200;
|
|
transform: translateX(-100%);
|
|
transition: transform .3s var(--ease);
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.side-menu.open {
|
|
transform: translateX(0);
|
|
}
|
|
|
|
.side-menu-mobile-header {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
padding: 8px 8px 0;
|
|
}
|
|
@media (min-width: 769px) { .side-menu-mobile-header { display: none; } }
|
|
|
|
.sidebar-nav {
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
padding: 12px;
|
|
}
|
|
|
|
.sidebar-collapse-bar {
|
|
flex-shrink: 0;
|
|
display: none;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 100%;
|
|
padding: 12px;
|
|
background: transparent;
|
|
border: none;
|
|
border-top: 1px solid var(--line-soft);
|
|
border-radius: 0;
|
|
color: var(--text-muted);
|
|
cursor: pointer;
|
|
transition: background .2s var(--ease), color .2s var(--ease);
|
|
}
|
|
.sidebar-collapse-bar:hover { background: var(--surface-sunk); color: var(--text); }
|
|
.sidebar-collapse-bar:active { transform: none; }
|
|
|
|
#sidebarCollapseIcon { transition: transform .2s var(--ease); }
|
|
|
|
/* === 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.
|
|
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 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;
|
|
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;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
padding-bottom: 10px;
|
|
}
|
|
.wand-zurueck {
|
|
appearance: none;
|
|
border: none;
|
|
background: none;
|
|
color: var(--text-subtle);
|
|
font-size: 12px;
|
|
padding: 0;
|
|
cursor: pointer;
|
|
}
|
|
.wand-zurueck:hover { color: var(--text-muted); }
|
|
|
|
/* wandIdleCountdown (js/wand/wand.js): die verbleibende Zeit bis zum
|
|
automatischen Rücksprung in den Normalzustand (WAND_IDLE_MS) — Erik wollte
|
|
sehen können, wann das passiert, statt davon überrascht zu werden. Sitzt
|
|
bewusst AUSSERHALB von #wandContent, damit sie von dessen Neu-Rendern
|
|
(jeder Datenabgleich, jedes Auf-/Zuklappen) unberührt bleibt. */
|
|
.wand-idle-countdown { flex-shrink: 0; font-size: 11px; color: var(--text-subtle); font-variant-numeric: tabular-nums; }
|
|
|
|
/* Normalzustand: #wandContent selbst bleibt genau ein Bildschirm hoch, kein
|
|
Scrollen (das ist der Zustand, den eine offene Übersicht ebenfalls nutzt —
|
|
siehe .wand-uebersicht). Erst .wand-scrollable (von renderWandboard()
|
|
gesetzt, wenn KEINE Übersicht offen ist) schaltet auf einen normalen
|
|
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; background: var(--dash-bg); }
|
|
.wand-grid-wrap.wand-scrollable { display: block; overflow-y: auto; }
|
|
.wand-ambient { min-height: 100%; display: flex; flex-direction: column; }
|
|
|
|
/* ===== 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; }
|
|
|
|
/* ===== 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); }
|
|
|
|
/* ===== 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(--dash-hover); }
|
|
.wand-meter-label { flex: 0 0 90px; font-size: 13px; font-weight: 600; color: var(--dash-text); }
|
|
.wand-meter {
|
|
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(--dash-text-subtle); padding-left: 4px; }
|
|
.wand-meter-wert {
|
|
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(--dash-text-subtle); margin-left: 2px; }
|
|
.wand-leer-klein { font-size: 11.5px; color: var(--dash-text-subtle); }
|
|
|
|
/* "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);
|
|
}
|
|
.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 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. 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(--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; 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(--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(--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 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
|
|
eigene Zeilenform hier) — nur der äußere Abstand zu den Kacheln darüber
|
|
ist eine Wandtafel-eigene Ergänzung. */
|
|
.wand-projektliste { margin-top: 22px; padding-bottom: 8px; }
|
|
.wand-projektliste .section-title:first-child { margin-top: 0; }
|
|
|
|
.side-menu-backdrop {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0,0,0,.35);
|
|
-webkit-backdrop-filter: blur(2px);
|
|
backdrop-filter: blur(2px);
|
|
z-index: 150;
|
|
opacity: 0;
|
|
visibility: hidden;
|
|
transition: opacity .3s var(--ease), visibility .3s var(--ease);
|
|
}
|
|
|
|
.side-menu.open ~ .side-menu-backdrop {
|
|
opacity: 1;
|
|
visibility: visible;
|
|
}
|
|
|
|
/* Ab 769px: Sidebar dauerhaft sichtbar (kein Off-Canvas mehr nötig) */
|
|
@media (min-width: 769px) {
|
|
#headerMenu.side-menu {
|
|
position: sticky;
|
|
/* top muss der tatsächlichen Höhe von .app-topbar entsprechen (beide
|
|
sticky, gleicher Scroll-Container) — sonst zieht die Sidebar bei
|
|
Seiten, die länger als ein Bildschirm sind, beim Herunterscrollen mit
|
|
top:0 unter das Menüband statt sich direkt darunter einzureihen, und
|
|
am unteren Rand bleibt eine sidebarlose Lücke in Höhe des Menübands
|
|
übrig (height: calc(100vh - 52px) reichte dann nicht bis nach unten). */
|
|
top: 52px;
|
|
height: calc(100vh - 52px);
|
|
transform: none;
|
|
box-shadow: none;
|
|
width: 260px;
|
|
flex: 0 0 260px;
|
|
border-right: 1px solid var(--line-soft);
|
|
}
|
|
#headerMenu.side-menu .header-toggle { display: none; }
|
|
.side-menu-backdrop { display: none; }
|
|
|
|
/* Umschalt-Leiste am unteren Sidebar-Rand nur auf Desktop-Breite sichtbar
|
|
(auf Mobile bleibt es beim reinen Off-Canvas-Menü ohne Zwischenzustand) */
|
|
.sidebar-collapse-bar { display: flex; }
|
|
|
|
/* Eingeklappte Sidebar: nur noch eine schmale Icon-Rail mit den 5
|
|
Navigationspunkten, alle Beschriftungen verschwinden */
|
|
#headerMenu.side-menu.collapsed {
|
|
width: 64px;
|
|
flex: 0 0 64px;
|
|
}
|
|
|
|
#headerMenu.side-menu.collapsed .sidebar-nav {
|
|
padding-left: 8px;
|
|
padding-right: 8px;
|
|
}
|
|
|
|
#headerMenu.side-menu.collapsed .nav-item .nav-label {
|
|
display: none;
|
|
}
|
|
|
|
#headerMenu.side-menu.collapsed .nav-item {
|
|
justify-content: center;
|
|
padding-left: 0;
|
|
padding-right: 0;
|
|
}
|
|
|
|
#headerMenu.side-menu.collapsed #sidebarCollapseIcon {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
/* In der Icon-Rail ergeben aufgeklappte Unterlisten/Rubrik-Überschriften
|
|
ohne Textbeschriftung keinen Sinn — beides wird vollständig ausgeblendet. */
|
|
#headerMenu.side-menu.collapsed .nav-chevron,
|
|
#headerMenu.side-menu.collapsed .sidebar-section-label,
|
|
#headerMenu.side-menu.collapsed .sidebar-subnav {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
.toolbar {
|
|
display: flex;
|
|
gap: 8px;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
}
|
|
|
|
button.active {
|
|
background: var(--accent);
|
|
color: #fff;
|
|
border-color: var(--accent);
|
|
}
|
|
|
|
/* Hamburger-Knopf im globalen Menüband (nur < 769px sichtbar, öffnet die
|
|
Sidebar als Off-Canvas-Menü, siehe .mobile-nav-toggle weiter oben). */
|
|
.menu-corner-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 40px;
|
|
height: 40px;
|
|
background: var(--surface-sunk);
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-md);
|
|
box-shadow: none;
|
|
color: var(--text);
|
|
cursor: pointer;
|
|
font-size: 18px;
|
|
transition: background .2s var(--ease);
|
|
}
|
|
|
|
.menu-corner-btn:hover { background: var(--line-soft); }
|
|
.menu-corner-btn:active { transform: none; }
|
|
|
|
/* Hauptnavigation (Sidebar) — ersetzt die alte horizontale .tabs-Leiste */
|
|
.sidebar-nav { gap: 2px; }
|
|
|
|
.nav-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
width: 100%;
|
|
text-align: left;
|
|
text-decoration: none;
|
|
background: transparent;
|
|
color: var(--text-muted);
|
|
padding: 9px 12px;
|
|
cursor: pointer;
|
|
border: none;
|
|
border-radius: var(--r-md);
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
transition: background .2s var(--ease), color .2s var(--ease);
|
|
}
|
|
|
|
.nav-icon {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 18px;
|
|
flex-shrink: 0;
|
|
font-size: 15px;
|
|
}
|
|
|
|
.nav-label {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* .nav-item kann ein echter <a href>-Link sein statt eines Buttons — ohne
|
|
diese Regel würde ein bereits besuchtes Ziel (echte Browser-Historie) mit
|
|
der UA-Standardfarbe für besuchte Links abweichen. */
|
|
.nav-item:visited { color: var(--text-muted); }
|
|
.nav-item:hover,
|
|
.nav-item:visited:hover { background: var(--surface-sunk); color: var(--text); }
|
|
|
|
.nav-item.active {
|
|
background: var(--accent-soft);
|
|
color: var(--accent);
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* .sidebar-section-label: kleine Rubrik-Überschrift oberhalb einer Gruppe von
|
|
Sidebar-Einträgen (z. B. "Dashboard", "Navigation"). */
|
|
.sidebar-section-label {
|
|
padding: 10px 12px 2px;
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
letter-spacing: .04em;
|
|
text-transform: uppercase;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
/* .nav-group-toggle: Sidebar-Eintrag, der statt eines Tabs einen
|
|
auf-/zuklappbaren Unterbereich steuert (Einstellungen/Admin, siehe
|
|
toggleSidebarAccordion() in js/ui.js). */
|
|
.nav-chevron {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 14px;
|
|
flex-shrink: 0;
|
|
margin-left: auto;
|
|
transition: transform .2s var(--ease);
|
|
}
|
|
|
|
.nav-group-toggle.expanded .nav-chevron { transform: rotate(180deg); }
|
|
|
|
/* .sidebar-subnav: Liste der Aktionen unter einem aufgeklappten
|
|
.nav-group-toggle, per max-height animiert ein-/ausgeblendet. */
|
|
.sidebar-subnav {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1px;
|
|
padding-left: 8px;
|
|
max-height: 0;
|
|
opacity: 0;
|
|
overflow: hidden;
|
|
transition: max-height .25s var(--ease), opacity .2s var(--ease);
|
|
}
|
|
|
|
.sidebar-subnav.open {
|
|
/* Reicht für die längste Gruppe („Einstellungen", sieben Punkte); die
|
|
Animation läuft über max-height, deshalb steht hier eine feste Zahl. */
|
|
max-height: 360px;
|
|
opacity: 1;
|
|
}
|
|
|
|
.sidebar-subitem {
|
|
width: 100%;
|
|
padding-left: 34px;
|
|
font-size: 12.5px;
|
|
color: var(--text-muted);
|
|
}
|
|
.sidebar-subitem:hover { color: var(--text); }
|
|
/* .is-active: der gerade gezeigte Abschnitt der Gruppe „Einstellungen"
|
|
(showSettingsSection() in js/settings/settings.js). Die übrigen Gruppen
|
|
lösen Aktionen aus und kennen deshalb keinen dauerhaft aktiven Punkt. */
|
|
.sidebar-subitem.is-active {
|
|
color: var(--accent);
|
|
font-weight: 600;
|
|
background: var(--accent-soft);
|
|
}
|
|
.sidebar-subitem.danger { color: var(--red); }
|
|
.sidebar-subitem.danger:hover { background: var(--red); color: #fff; }
|
|
|
|
/* === Buttons & Formulare === */
|
|
button {
|
|
cursor: pointer;
|
|
border: none;
|
|
border-radius: var(--r-pill);
|
|
padding: 7px 16px;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
font-family: inherit;
|
|
background: var(--accent);
|
|
color: #fff;
|
|
letter-spacing: -0.01em;
|
|
transition: background .2s var(--ease), transform .1s var(--ease),
|
|
box-shadow .2s var(--ease);
|
|
}
|
|
|
|
button:hover { background: var(--accent-hover); }
|
|
button:active { background: var(--accent-active); transform: scale(.97); }
|
|
|
|
button:focus-visible {
|
|
outline: none;
|
|
box-shadow: 0 0 0 3px rgba(0,113,227,.35);
|
|
}
|
|
|
|
button.secondary {
|
|
background: rgba(0,0,0,.06);
|
|
color: var(--text);
|
|
}
|
|
button.secondary:hover { background: rgba(0,0,0,.10); }
|
|
button.secondary:active { background: rgba(0,0,0,.14); }
|
|
|
|
button.danger { background: var(--red); }
|
|
button.danger:hover { background: var(--red-hover); }
|
|
button.danger:active { background: var(--red-active); }
|
|
|
|
button.small {
|
|
padding: 3px 10px;
|
|
font-size: 11px;
|
|
}
|
|
|
|
select,
|
|
input[type=text],
|
|
input[type=email],
|
|
input[type=date] {
|
|
padding: 7px 10px;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--r-md);
|
|
font-size: 13px;
|
|
font-family: inherit;
|
|
color: var(--text);
|
|
background: var(--surface);
|
|
transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
|
|
}
|
|
|
|
select:focus,
|
|
input[type=text]:focus,
|
|
input[type=email]:focus,
|
|
input[type=date]:focus {
|
|
outline: none;
|
|
border-color: var(--accent);
|
|
box-shadow: 0 0 0 3px rgba(0,113,227,.18);
|
|
}
|
|
|
|
select {
|
|
-webkit-appearance: none;
|
|
-moz-appearance: none;
|
|
appearance: none;
|
|
padding-right: 28px;
|
|
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath fill='%236e6e73' d='M0 0h10L5 6z'/%3E%3C/svg%3E");
|
|
background-repeat: no-repeat;
|
|
background-position: right 10px center;
|
|
background-color: var(--surface);
|
|
}
|
|
|
|
.role-checkboxes {
|
|
display: flex;
|
|
gap: 12px;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
}
|
|
|
|
.role-checkboxes label {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
input[type=checkbox] { accent-color: var(--accent); }
|
|
|
|
/* === Tabellen & Matrix === */
|
|
table {
|
|
border-collapse: separate;
|
|
border-spacing: 0;
|
|
width: 100%;
|
|
background: var(--surface);
|
|
font-size: 12px;
|
|
}
|
|
|
|
th,
|
|
td {
|
|
border: 1px solid var(--line-soft);
|
|
padding: 5px 7px;
|
|
text-align: center;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
thead tr:nth-child(1) th {
|
|
background: var(--surface-sunk);
|
|
color: var(--text);
|
|
font-weight: 600;
|
|
border: 1px solid var(--line);
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 10;
|
|
}
|
|
|
|
tr:nth-child(even) td { background: var(--surface-alt); }
|
|
|
|
/* === Status-Felder in der Projektzeile === */
|
|
.status {
|
|
min-width: 240px;
|
|
position: relative;
|
|
vertical-align: top;
|
|
}
|
|
|
|
.status-edit {
|
|
width: 100%;
|
|
min-height: 34px;
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-sm);
|
|
padding: 5px 7px;
|
|
font-size: 11px;
|
|
font-family: inherit;
|
|
line-height: 1.4;
|
|
color: var(--text);
|
|
background: var(--surface);
|
|
text-align: left;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
|
|
}
|
|
|
|
.status-edit:focus {
|
|
outline: none;
|
|
border-color: var(--accent);
|
|
box-shadow: 0 0 0 3px rgba(0,113,227,.18);
|
|
}
|
|
|
|
.status-edit.status-new { color: var(--green); }
|
|
.status-edit.status-duplicated { color: var(--text); }
|
|
.status-edit p { margin: 0 0 4px; }
|
|
.status-edit p:last-child { margin-bottom: 0; }
|
|
.status-edit b,
|
|
.status-edit strong { font-weight: 600; }
|
|
|
|
/* === Kommentar-Editor im Board und im Wochenbericht ======================
|
|
Ersetzt das frühere contenteditable-Feld samt execCommand-Leiste: Textfeld
|
|
mit Verfasser-Kopfzeile, ausdrücklichem Speichern und dem eingeklappten,
|
|
scrollbaren Verlauf der früheren Sitzungen. Aufgebaut von
|
|
renderCommentEditor() in js/board/board.js. */
|
|
|
|
.comment-block { display: flex; flex-direction: column; gap: 10px; }
|
|
|
|
.comment-editor {
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--r-md);
|
|
background: var(--surface);
|
|
overflow: hidden;
|
|
}
|
|
.comment-editor-head,
|
|
.comment-head-line {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
flex-wrap: wrap;
|
|
padding: 6px 10px;
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
background: var(--surface-alt);
|
|
border-bottom: 1px solid var(--line-soft);
|
|
}
|
|
.comment-head-line { border: 1px solid var(--line-soft); border-radius: var(--r-md); }
|
|
.comment-who { display: flex; align-items: center; gap: 6px; color: var(--text); font-weight: 500; }
|
|
.comment-unknown { color: var(--text-subtle); font-weight: 400; font-style: italic; }
|
|
.comment-sep { color: var(--text-subtle); }
|
|
.comment-av {
|
|
width: 19px; height: 19px; border-radius: 50%;
|
|
background: var(--accent); color: #fff;
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-size: 9px; font-weight: 600; flex-shrink: 0;
|
|
}
|
|
.comment-kbd { margin-left: auto; font-size: 10px; color: var(--text-subtle); white-space: nowrap; }
|
|
|
|
/* .comment-field ist seit der Umstellung auf contenteditable (Eriks Wunsch
|
|
nach echter statt mit Sternchen markierter Fett-/Kursiv-/Farbschrift,
|
|
public/js/utils.js) ein <div>, kein <textarea> mehr — Selektor deshalb
|
|
klassenbasiert statt elementbasiert, resize entfällt (kein Äquivalent für
|
|
contenteditable), Platzhalter über :empty:before + data-placeholder statt
|
|
::placeholder (das Pseudo-Element gibt es nur für echte Formularfelder). */
|
|
.comment-editor .comment-field {
|
|
width: 100%;
|
|
display: block;
|
|
padding: 8px 10px;
|
|
font-family: inherit;
|
|
font-size: 12px;
|
|
line-height: 1.5;
|
|
color: var(--text);
|
|
background: var(--surface);
|
|
min-height: 62px;
|
|
}
|
|
.comment-editor .comment-field:focus { outline: none; box-shadow: none; }
|
|
.comment-editor .comment-field:empty::before { content: attr(data-placeholder); color: var(--text-subtle); }
|
|
|
|
/* .richtext-toolbar (js/utils.js, richtextToolbarHtml()): gemeinsame
|
|
Fett/Kursiv/Farbe-Werkzeugleiste für jedes Textfeld, das Eriks Wunsch nach
|
|
formatierbarem Text bedient (Board-Kommentar, Protokoll-Punkt-Text — siehe
|
|
.richtext-wrap dort). "Angedockte" Darstellung (Eriks Auswahl C aus dem
|
|
Formatierungsleiste-Mockup): eine schattierte Leiste, die direkt auf dem
|
|
Textfeld aufsitzt — eigene Fläche + untere Trennkante statt einzeln
|
|
umrandeter Knöpfe, damit Leiste und Feld wie EIN Kasten statt zwei lose
|
|
übereinanderstehende Elemente wirken. Bewusst KEIN zusätzlicher Rahmen um
|
|
den ganzen .richtext-wrap: das Protokoll-Textfeld soll weiterhin randlos in
|
|
seiner Tabellenzelle sitzen (siehe Kommentar bei .protokoll-thema-cell
|
|
weiter unten), der Board-Kommentar steckt ohnehin schon in der
|
|
umrandeten .comment-editor-Karte — die Leiste muss daher in beiden
|
|
Zusammenhängen funktionieren, ohne selbst Ecken abzurunden (mal steht sie
|
|
ganz oben in ihrer Karte, mal nicht). */
|
|
.richtext-toolbar {
|
|
display: flex; align-items: center; gap: 8px; padding: 5px 8px;
|
|
background: var(--surface-sunk); border-bottom: 1px solid var(--line-soft);
|
|
}
|
|
.richtext-btn-group { display: inline-flex; gap: 2px; }
|
|
.richtext-btn {
|
|
appearance: none; border: none; background: none; color: var(--text-muted);
|
|
width: 22px; height: 20px; border-radius: 4px; font-size: 11px; font-weight: 700; cursor: pointer; line-height: 1;
|
|
}
|
|
.richtext-btn:hover { background: var(--surface-alt); color: var(--text); }
|
|
.richtext-divider { width: 1px; height: 14px; background: var(--line); flex-shrink: 0; }
|
|
.richtext-farben { display: inline-flex; gap: 6px; }
|
|
.richtext-farbe-btn {
|
|
appearance: none; border: none; width: 14px; height: 14px; border-radius: 50%; cursor: pointer; padding: 0;
|
|
box-shadow: 0 0 0 1px rgba(0,0,0,.08) inset;
|
|
}
|
|
.richtext-farbe-btn:hover { outline: 2px solid var(--line); outline-offset: 1px; }
|
|
/* .richtext-hinweis: Fett/Kursiv/Farbe wirken nur auf MARKIERTEN Text — ohne
|
|
Markierung tut ein Klick nichts, und das war bisher nirgends ablesbar.
|
|
Solange nichts markiert ist, bleiben die Knöpfe blass und der Hinweis
|
|
fordert zum Markieren auf; .hat-auswahl (von einem selectionchange-
|
|
Lauscher gesetzt, siehe js/plan/plan-protokoll.js) dreht beides um. */
|
|
.richtext-hinweis { margin-left: auto; font-size: 10px; color: var(--text-subtle); white-space: nowrap; }
|
|
.richtext-toolbar:not(.hat-auswahl) .richtext-btn,
|
|
.richtext-toolbar:not(.hat-auswahl) .richtext-farbe-btn { opacity: .45; }
|
|
.richtext-toolbar.hat-auswahl { background: var(--accent-soft); }
|
|
.richtext-toolbar.hat-auswahl .richtext-hinweis { color: var(--accent); font-weight: 600; }
|
|
|
|
.comment-editor-foot {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 7px;
|
|
padding: 7px 10px;
|
|
border-top: 1px solid var(--line-soft);
|
|
background: var(--surface-alt);
|
|
}
|
|
.comment-foot-spacer { margin-left: auto; }
|
|
.comment-dirty { font-size: 10.5px; color: var(--amber); white-space: nowrap; }
|
|
|
|
.comment-readonly {
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-md);
|
|
padding: 7px 9px;
|
|
font-size: 11.5px;
|
|
line-height: 1.45;
|
|
background: var(--surface);
|
|
}
|
|
|
|
/* Verlauf: eingeklappt, mit Anzahl im Kopf; die Liste selbst scrollt */
|
|
.comment-history {
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-md);
|
|
background: var(--surface);
|
|
overflow: hidden;
|
|
}
|
|
.comment-history > summary {
|
|
cursor: pointer;
|
|
list-style: none;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 7px 10px;
|
|
font-size: 11.5px;
|
|
color: var(--text);
|
|
user-select: none;
|
|
}
|
|
.comment-history > summary::-webkit-details-marker { display: none; }
|
|
.comment-history > summary:hover { background: var(--surface-alt); }
|
|
.comment-history-caret {
|
|
color: var(--text-subtle);
|
|
font-size: 9px;
|
|
transition: transform .18s var(--ease);
|
|
}
|
|
.comment-history[open] > summary .comment-history-caret { transform: rotate(90deg); }
|
|
.comment-history-count {
|
|
margin-left: auto;
|
|
font-size: 10.5px;
|
|
font-weight: 600;
|
|
background: var(--accent-soft);
|
|
color: var(--accent);
|
|
border-radius: var(--r-pill);
|
|
padding: 1px 8px;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.comment-history-count.zero { background: var(--surface-sunk); color: var(--text-subtle); }
|
|
.comment-history-body { border-top: 1px solid var(--line-soft); padding: 4px 10px 8px; }
|
|
|
|
.comment-history-list {
|
|
max-height: 220px;
|
|
overflow-y: auto;
|
|
overscroll-behavior: contain;
|
|
}
|
|
.comment-history-entry { padding: 8px 0; }
|
|
.comment-history-entry + .comment-history-entry { border-top: 1px solid var(--line-soft); }
|
|
.comment-history-head {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-bottom: 3px;
|
|
font-size: 10.5px;
|
|
}
|
|
.comment-history-week {
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
white-space: nowrap;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.comment-history-meta { color: var(--text-subtle); }
|
|
.comment-history-text { font-size: 11.5px; line-height: 1.45; color: var(--text-muted); }
|
|
.comment-history-text b,
|
|
.comment-history-text strong { font-weight: 600; color: var(--text); }
|
|
|
|
.comment-take {
|
|
margin-left: auto;
|
|
padding: 2px 9px;
|
|
font-size: 10px;
|
|
border-radius: var(--r-pill);
|
|
background: transparent;
|
|
color: var(--text-muted);
|
|
border: 1px solid var(--line);
|
|
flex-shrink: 0;
|
|
}
|
|
.comment-take:hover { background: var(--surface-sunk); color: var(--text); }
|
|
|
|
.status-toolbar {
|
|
display: none;
|
|
position: absolute;
|
|
bottom: 100%;
|
|
left: 0;
|
|
margin-bottom: 4px;
|
|
background: rgba(255,255,255,.9);
|
|
-webkit-backdrop-filter: saturate(180%) blur(16px);
|
|
backdrop-filter: saturate(180%) blur(16px);
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-md);
|
|
padding: 3px;
|
|
gap: 2px;
|
|
box-shadow: var(--sh-2);
|
|
z-index: 10;
|
|
}
|
|
|
|
.status:focus-within .status-toolbar { display: flex; }
|
|
|
|
.status-toolbar button {
|
|
padding: 3px 7px;
|
|
font-size: 11px;
|
|
line-height: 1.2;
|
|
min-width: 24px;
|
|
border-radius: var(--r-sm);
|
|
background: transparent;
|
|
color: var(--text);
|
|
border: none;
|
|
}
|
|
|
|
.status-toolbar button:hover { background: rgba(0,0,0,.06); }
|
|
.status-toolbar button:active { background: rgba(0,0,0,.10); transform: none; }
|
|
|
|
.status-toolbar button.color-red { color: var(--red); }
|
|
.status-toolbar button.color-green { color: var(--green); }
|
|
.status-toolbar button.color-blue { color: var(--accent); }
|
|
.status-toolbar button.color-black {
|
|
color: var(--text);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.locked-cell {
|
|
cursor: not-allowed !important;
|
|
background: var(--surface-sunk) !important;
|
|
opacity: .65;
|
|
}
|
|
|
|
.locked-cell input {
|
|
background: var(--surface-sunk) !important;
|
|
cursor: not-allowed !important;
|
|
border-color: var(--line-soft) !important;
|
|
}
|
|
|
|
.bar {
|
|
height: 5px;
|
|
border-radius: var(--r-pill);
|
|
background: var(--line-soft);
|
|
margin-top: 4px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.bar > div {
|
|
height: 100%;
|
|
background: var(--green);
|
|
border-radius: var(--r-pill);
|
|
transition: width .3s var(--ease);
|
|
}
|
|
|
|
/* === Projektboard (Listenansicht) ===
|
|
Eine Bubble-Card je Projektgruppe (.board-list) mit den Projekten als
|
|
Listenzeilen (.board-row) — ersetzt die frühere breite Matrix (eine Spalte
|
|
je Person/Leistungsphase). Jede Zeile zeigt nur das Nötigste auf einen
|
|
Blick (Kürzel-Avatare der Zugeordneten, aktive Phase, Status-Vorschau);
|
|
Klick auf die Zeile klappt darunter das Detailpanel (.board-drawer) mit
|
|
Status-Editor, vollständiger Rollen-Zuordnung und allen LPH-Balken auf. */
|
|
.board-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: var(--surface);
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-xl);
|
|
box-shadow: var(--sh-2);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.board-row { border-bottom: 1px solid var(--line-soft); }
|
|
.board-row:last-child { border-bottom: none; }
|
|
|
|
.board-row-main {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 14px;
|
|
padding: 12px 16px;
|
|
cursor: pointer;
|
|
transition: background .15s var(--ease);
|
|
}
|
|
.board-row-main:hover { background: var(--surface-sunk); }
|
|
|
|
.board-chev {
|
|
flex-shrink: 0;
|
|
color: var(--text-subtle);
|
|
display: inline-flex;
|
|
transition: transform .2s var(--ease);
|
|
}
|
|
.board-row.open .board-chev { transform: rotate(90deg); }
|
|
|
|
.board-nr {
|
|
flex-shrink: 0;
|
|
width: 64px;
|
|
font-size: 11.5px;
|
|
color: var(--text-muted);
|
|
font-family: ui-monospace, 'SF Mono', monospace;
|
|
}
|
|
|
|
.board-name-wrap { flex: 1 1 260px; min-width: 0; }
|
|
.board-name {
|
|
font-weight: 600;
|
|
letter-spacing: -0.01em;
|
|
cursor: pointer;
|
|
}
|
|
.board-name:hover { color: var(--accent); text-decoration: underline; }
|
|
|
|
.board-status-line {
|
|
margin-top: 3px;
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.board-newdot {
|
|
flex-shrink: 0;
|
|
width: 7px;
|
|
height: 7px;
|
|
border-radius: 50%;
|
|
background: var(--green);
|
|
}
|
|
|
|
.board-avatar-stack {
|
|
flex-shrink: 0;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 4px;
|
|
max-width: 220px;
|
|
}
|
|
|
|
.board-avatar {
|
|
flex-shrink: 0;
|
|
width: 26px;
|
|
height: 26px;
|
|
border-radius: 50%;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 10px;
|
|
font-weight: 700;
|
|
letter-spacing: -0.02em;
|
|
color: var(--text-muted);
|
|
background: var(--surface-sunk);
|
|
border: 1px solid var(--line-soft);
|
|
cursor: pointer;
|
|
user-select: none;
|
|
transition: background .15s var(--ease), color .15s var(--ease), box-shadow .15s var(--ease);
|
|
}
|
|
|
|
.board-avatar.role-planung.state-1 { background: var(--green); border-color: var(--green); color: #fff; }
|
|
.board-avatar.role-projektleitung.state-1 { background: var(--red); border-color: var(--red); color: #fff; }
|
|
.board-avatar.role-bauüberwachung.state-1 { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
|
|
.board-avatar.role-planung.state-2 { background: transparent; color: var(--green); border-color: var(--green); }
|
|
.board-avatar.role-projektleitung.state-2 { background: transparent; color: var(--red); border-color: var(--red); }
|
|
.board-avatar.role-bauüberwachung.state-2 { background: transparent; color: var(--accent); border-color: var(--accent); }
|
|
|
|
.phase-pill {
|
|
flex-shrink: 0;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
font-family: ui-monospace, 'SF Mono', monospace;
|
|
padding: 4px 10px;
|
|
border-radius: var(--r-pill);
|
|
background: var(--accent-soft);
|
|
color: var(--accent);
|
|
white-space: nowrap;
|
|
}
|
|
.phase-pill.muted { background: var(--surface-sunk); color: var(--text-subtle); font-family: inherit; font-weight: 500; }
|
|
|
|
/* --- Detailpanel (.board-drawer) --- */
|
|
.board-drawer {
|
|
padding: 18px 24px 22px;
|
|
background: var(--surface-alt);
|
|
border-top: 1px solid var(--line-soft);
|
|
}
|
|
|
|
.board-drawer h4 {
|
|
margin: 0 0 8px;
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: .05em;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
/* Die Schublade liefert drei gleichrangige Blöcke (Kommentar, Leistungsphasen,
|
|
Beteiligte, siehe renderBoardDrawer() in js/board/board.js). Im Projektboard
|
|
stehen alle drei übereinander: Kommentar über die volle Breite (er teilte
|
|
sich zuvor eine schmale Spalte mit den gestapelten LPH-Zeilen, die ihn auf
|
|
wenig Höhe drückten — Eriks Rückmeldung "Darstellung der Kommentare ist zu
|
|
klein"), darunter die Leistungsphasen als EIN schmaler Streifen
|
|
(board-lph-leiste, dasselbe Muster wie in der dichten Wochenbericht-Karte)
|
|
statt als Liste, darunter die Beteiligten. */
|
|
.board-drawer-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr;
|
|
grid-template-areas: "kommentar" "lph" "beteiligte";
|
|
align-content: start;
|
|
gap: 18px;
|
|
}
|
|
.drawer-col-comment { grid-area: kommentar; }
|
|
.drawer-col-lph { grid-area: lph; }
|
|
.drawer-col-people { grid-area: beteiligte; }
|
|
|
|
/* Auswahl der beauftragten Leistungsphasen. Genutzt vom Honorar-Dialog der
|
|
Projektplan-Seite (renderHonorarEingaben(), js/plan/plan-honorar.js) und
|
|
vom Neues-Projekt-Modal (openNewProjModal(), js/load/load.js) — deshalb
|
|
hier im gemeinsamen Stylesheet statt inline in einer der beiden Seiten. */
|
|
.beauftragte-lp-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
|
|
gap: 4px 10px;
|
|
}
|
|
.beauftragte-lp-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
font-size: 12px;
|
|
color: var(--text);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* --- Wochenbericht: immer aufgeklappte Projekt-Karten ("Neu kommentiert",
|
|
"Fortschritt aktualisiert") — im Unterschied zu .board-list/.board-row
|
|
(Akkordeon der "Unverändert"-Gruppe) ist hier jede Karte ein eigenständiger
|
|
Kasten, kein Container mit Trennlinien. */
|
|
.report-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
.report-card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-md);
|
|
box-shadow: var(--sh-1);
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Kopfband: durchgefärbt in der Farbe der Änderungsart, damit beim Scrollen
|
|
ohne Blick auf die Abschnitts-Überschrift erkennbar ist, worum es geht.
|
|
Die Farbe kommt aus --band-neu/--band-fortschritt und lässt sich dort in
|
|
einem Zug abschwächen, falls die Leiste zu kräftig wirkt. */
|
|
.report-card-head {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
padding: 9px 14px;
|
|
background: var(--surface-alt);
|
|
border-bottom: 1px solid var(--line-soft);
|
|
}
|
|
.report-card-head .board-name { cursor: pointer; }
|
|
|
|
.report-card-neu > .report-band { background: var(--band-neu); }
|
|
.report-card-fortschritt > .report-band { background: var(--band-fortschritt); }
|
|
|
|
.report-band {
|
|
border-bottom: none;
|
|
color: #fff;
|
|
}
|
|
.report-band .board-nr { color: rgba(255,255,255,.75); }
|
|
.report-band .board-name { color: #fff; font-weight: 600; }
|
|
.report-band .board-name:hover { text-decoration: underline; }
|
|
.report-band .tag-chip {
|
|
background: rgba(255,255,255,.22);
|
|
color: #fff;
|
|
border-color: transparent;
|
|
}
|
|
.report-band .report-reason-badge {
|
|
background: rgba(255,255,255,.22);
|
|
color: #fff;
|
|
}
|
|
|
|
/* Drei Spalten statt zwei: im Bericht stehen Leistungsphasen und Beteiligte
|
|
nebeneinander, wodurch die Karte flacher wird. Dasselbe Markup wie im
|
|
Projektboard, nur anders angeordnet. */
|
|
.report-card .board-drawer {
|
|
padding: 14px 16px 16px;
|
|
border-top: none;
|
|
background: var(--surface);
|
|
}
|
|
.report-card .board-drawer-grid {
|
|
grid-template-columns: 1.7fr 1fr 1fr;
|
|
grid-template-areas: "kommentar lph beteiligte";
|
|
gap: 22px;
|
|
}
|
|
.report-card .board-role-label { width: 58px; }
|
|
|
|
@media (max-width: 860px) {
|
|
.report-card .board-drawer-grid {
|
|
grid-template-columns: 1fr;
|
|
grid-template-areas: "kommentar" "lph" "beteiligte";
|
|
gap: 16px;
|
|
}
|
|
}
|
|
|
|
/* === Dichte Fassung der Karte (.board-drawer-dicht, Wochenbericht) ===
|
|
Fokus-Modus, zweite Fassung (Eriks Rückmeldung zum Entwurfsvergleich "A -
|
|
Aktenregal"): der Kommentar bleibt das größte, offene Element; Leistungs-
|
|
phasen und Beteiligte stehen darunter dauerhaft sichtbar nebeneinander
|
|
(.bericht-meta-grid, siehe renderBoardDrawer()s dichte Fassung in
|
|
js/board/board.js) statt in aufklappbaren Abschnitten versteckt zu sein -
|
|
"die Beteiligten sollen mit angezeigt werden". Die erste Fassung hatte
|
|
beide hinter Aufklappern verborgen (gegen die Rückmeldung "zu überladen"
|
|
bei allem gleichzeitig sichtbar); jetzt sind beide bewusst KOMPAKT
|
|
gehalten (Kästchen-Leiste bzw. schmale Rollenzeilen statt breiter
|
|
Balkenzeilen), damit sie nebeneinander Platz finden, ohne den Kommentar zu
|
|
überragen. Nur die Anwesenheit (renderAttendance(),
|
|
js/wochenbericht/wochenbericht.js) bleibt ein Aufklapper
|
|
(.bericht-disclosure, weiter unten) — sie ist eine Sitzungs-, keine
|
|
Projekt-Angabe und sitzt ohnehin außerhalb dieser Karte am Seitenfuß. */
|
|
.board-drawer-dicht { padding: 0; background: transparent; border-top: none; }
|
|
.bericht-meta-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line-soft); }
|
|
@media (max-width: 860px) { .bericht-meta-grid { grid-template-columns: 1fr; gap: 16px; } }
|
|
.bericht-meta-label { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--text-subtle); margin-bottom: 8px; }
|
|
|
|
.bericht-disclosure {
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-md);
|
|
margin-top: 10px;
|
|
overflow: hidden;
|
|
background: var(--surface);
|
|
}
|
|
.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);
|
|
}
|
|
.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; }
|
|
|
|
|
|
/* 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;
|
|
gap: 3px;
|
|
align-items: stretch;
|
|
}
|
|
.board-lph-cell {
|
|
flex: 1 1 0;
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 1px;
|
|
padding: 3px 2px 4px;
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-sm);
|
|
background: var(--surface-sunk);
|
|
/* Der Füllstand kommt als senkrechter Verlauf aus dem Inline-Stil
|
|
(renderBoardDrawer in js/board/board.js) — so bleibt der Balken erhalten,
|
|
den die Zeilenform als eigenes Element hatte. */
|
|
background-repeat: no-repeat;
|
|
}
|
|
.board-lph-cell-label {
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
color: var(--text-muted);
|
|
line-height: 1.2;
|
|
}
|
|
.board-lph-cell input {
|
|
width: 100%;
|
|
min-width: 0;
|
|
padding: 1px 2px;
|
|
border: none;
|
|
background: transparent;
|
|
text-align: center;
|
|
font-size: 11.5px;
|
|
font-variant-numeric: tabular-nums;
|
|
color: var(--text);
|
|
font-family: inherit;
|
|
}
|
|
.board-lph-cell input:focus {
|
|
outline: 1.5px solid var(--accent);
|
|
outline-offset: -1px;
|
|
border-radius: 3px;
|
|
}
|
|
.board-lph-cell.lp-aktiv {
|
|
border-color: var(--accent);
|
|
}
|
|
.board-lph-cell.lp-aktiv .board-lph-cell-label,
|
|
.board-lph-cell.lp-aktiv input { color: var(--accent); }
|
|
.board-lph-cell.locked-cell input { cursor: default; }
|
|
|
|
/* Das Rollen-Label steht NEBEN seinen Kürzeln. Über ihnen kostete es drei
|
|
zusätzliche Zeilen, und damit bestimmte die Beteiligten-Spalte die Höhe der
|
|
ganzen Karte (189 px gegenüber 130 für den Kommentar). Die feste Breite ist
|
|
so bemessen, dass "Bauüberwachung" hineinpasst — bei 52 px lief die
|
|
Beschriftung über die Kürzel.
|
|
|
|
Die Regeln hängen am Merkmal .board-drawer-dicht, nicht am Ort: Sie
|
|
standen zunächst unter .report-card, und mit dem Umbau des Wochenberichts
|
|
auf die Tagesordnung gab es diesen Container nicht mehr — die dichte
|
|
Karte fiel still auf die Board-Darstellung zurück. Das zusätzliche
|
|
.board-role-mini liefert die Spezifität, die nötig ist, um die dortige
|
|
Regel für .board-role-row zu schlagen. */
|
|
.board-drawer-dicht .board-role-mini .board-role-row {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 7px;
|
|
margin-bottom: 4px;
|
|
}
|
|
.board-drawer-dicht .board-role-mini .board-role-label {
|
|
flex: 0 0 auto;
|
|
/* Bemessen am längsten Rollennamen: "BAUÜBERWACHUNG" braucht bei 10 px in
|
|
Versalien diese Breite — bei 88 px fehlte der letzte Buchstabe. */
|
|
width: 104px;
|
|
padding-top: 4px;
|
|
font-size: 10px;
|
|
line-height: 1.25;
|
|
letter-spacing: .02em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
/* Schmale Fenster: Dort reicht die Breite für Label UND Kürzel nicht mehr,
|
|
also wieder übereinander — lieber drei Zeilen mehr als abgeschnittene
|
|
Beschriftungen. */
|
|
@media (max-width: 1100px) {
|
|
.board-drawer-dicht .board-role-mini .board-role-row { display: block; }
|
|
.board-drawer-dicht .board-role-mini .board-role-label {
|
|
width: auto;
|
|
padding-top: 0;
|
|
margin-bottom: 2px;
|
|
}
|
|
}
|
|
|
|
.report-reason-badge {
|
|
margin-left: auto;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
padding: 3px 10px;
|
|
border-radius: var(--r-pill);
|
|
background: var(--accent-soft);
|
|
color: var(--accent);
|
|
white-space: nowrap;
|
|
}
|
|
/* Innerhalb einer Report-Karte braucht das Detailpanel nicht den
|
|
Zeilen-Einzug/die obere Trennlinie, die es im Board-Akkordeon ausrichtet. */
|
|
.report-card .board-drawer {
|
|
padding: 16px 20px 20px;
|
|
border-top: none;
|
|
}
|
|
|
|
/* .board-lph-row/-list/-label/-wert: reine Anzeige-Zeile für Leistungsphasen,
|
|
seit Variante A (Board-Drawer) nicht mehr für den Drawer selbst gebraucht
|
|
(siehe .board-lph-leiste/-cell dort), aber weiterhin für die
|
|
Fortschrittsliste im Projektbereich-Dashboard (#planLphList,
|
|
renderPlanInfo() in js/plan/plan-info.js) — eine großzügig bemessene,
|
|
eigene Fläche statt einer schmalen Drawer-Spalte, wo die gestapelte Form
|
|
kein Platzproblem hat. */
|
|
.board-lph-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 7px;
|
|
margin-top: 14px;
|
|
}
|
|
|
|
.board-lph-row {
|
|
display: grid;
|
|
grid-template-columns: 42px 1fr 50px;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
.board-lph-row.is-ansicht{ cursor: default; }
|
|
|
|
.board-lph-row .bar { flex: 1; margin-top: 0; }
|
|
|
|
.board-lph-label {
|
|
font-size: 11px;
|
|
font-family: ui-monospace, 'SF Mono', monospace;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.board-lph-row.lp-aktiv .board-lph-label { color: var(--accent); font-weight: 600; }
|
|
|
|
/* Der Prozentwert einer Leistungsphase an der Stelle des Eingabefeldes. Die
|
|
Breite entspricht dem Feld, damit die Balken aller Zeilen gleich weit
|
|
reichen; tabular-nums hält die Ziffern in einer Spalte. */
|
|
.board-lph-wert{
|
|
min-width: 52px;
|
|
text-align: right;
|
|
font-size: 12px;
|
|
font-variant-numeric: tabular-nums;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.board-role-mini { display: flex; flex-direction: column; gap: 10px; }
|
|
.board-role-mini .board-role-row { display: flex; align-items: center; gap: 10px; }
|
|
.board-role-mini .board-role-label {
|
|
width: 100px;
|
|
flex-shrink: 0;
|
|
font-size: 11.5px;
|
|
color: var(--text-muted);
|
|
}
|
|
.board-role-mini .board-role-chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
|
|
.section-title {
|
|
font-size: 17px;
|
|
font-weight: 600;
|
|
margin: 24px 0 10px;
|
|
color: var(--text);
|
|
letter-spacing: -0.02em;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
/* === Lade-/Auswertungsübersicht === */
|
|
.load-grid {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 24px;
|
|
width: 100%;
|
|
}
|
|
|
|
.load-row {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
gap: 16px;
|
|
align-items: start;
|
|
width: 100%;
|
|
}
|
|
|
|
/* === Auslastung: das Lastband ===
|
|
Eine Zeile je Person und Rolle; der Balken ist nach Projekten aufgeteilt.
|
|
Zuvor stand jede Person in einer eigenen .load-card mit ihrer Projektliste
|
|
klein darunter — man sah die Zahl, aber nicht, woraus sie besteht. Die
|
|
Frage nach einer Auslastung von 1,80 ist aber immer dieselbe: aus einem
|
|
großen Projekt oder aus fünf kleinen, und was davon lässt sich abgeben?
|
|
.load-card/.load-row/.load-num/.load-detail bleiben bestehen — das
|
|
Dashboard (js/dashboard/dashboard.js) und die Ressourcenansicht des
|
|
Projektbereichs (js/plan/plan-gantt.js) bauen darauf auf. */
|
|
.load-rolle { width: 100%; }
|
|
|
|
.load-rolle-titel {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
margin: 0 0 6px;
|
|
color: var(--text-muted);
|
|
text-transform: uppercase;
|
|
letter-spacing: .04em;
|
|
}
|
|
|
|
.load-zeile {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
padding: 11px 4px;
|
|
border-bottom: 1px solid var(--line-soft);
|
|
}
|
|
.load-zeile:last-child { border-bottom: none; }
|
|
.load-zeile:hover { background: var(--surface); }
|
|
|
|
/* Wer — Name oben, Kürzel und Zusätze darunter. Feste Breite, damit die
|
|
Bänder aller Zeilen an derselben Stelle beginnen und vergleichbar sind. */
|
|
.load-wer {
|
|
flex: 0 0 200px;
|
|
min-width: 0;
|
|
}
|
|
.load-wer-name {
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.load-wer-meta {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
flex-wrap: wrap;
|
|
font-size: 11px;
|
|
color: var(--text-subtle);
|
|
margin-top: 1px;
|
|
}
|
|
|
|
/* Das Band. Die Segmente füllen es von links; was frei bleibt, ist die
|
|
Restkapazität bis zum Skalenende (Vorgabe 8, firmenweit einstellbar). */
|
|
.load-band {
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
height: 22px;
|
|
min-width: 140px;
|
|
background: var(--surface-sunk);
|
|
border-radius: var(--r-sm);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.load-seg {
|
|
display: flex;
|
|
align-items: center;
|
|
height: 100%;
|
|
padding: 0 7px;
|
|
min-width: 3px;
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
color: #fff;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
border-right: 1px solid rgba(255,255,255,.4);
|
|
transition: filter .15s var(--ease);
|
|
}
|
|
.load-seg:last-of-type { border-right: none; }
|
|
.load-seg:hover { filter: brightness(1.12); }
|
|
.load-seg span { overflow: hidden; }
|
|
|
|
.load-band-leer {
|
|
font-size: 11px;
|
|
color: var(--text-subtle);
|
|
padding-left: 9px;
|
|
}
|
|
|
|
.load-wert {
|
|
flex: 0 0 62px;
|
|
text-align: right;
|
|
font-size: 19px;
|
|
font-weight: 600;
|
|
letter-spacing: -.02em;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
.load-anbahnung {
|
|
font-size: 10px;
|
|
padding: 1px 7px;
|
|
border-radius: var(--r-pill);
|
|
background: var(--surface-sunk);
|
|
color: var(--text-muted);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Kopfzeile: was die Zahlen abdecken. Sie steht oben, weil die Aussagekraft
|
|
der ganzen Seite daran hängt — eine Auslastung aus drei von 29 Projekten
|
|
ist etwas anderes als eine aus 29. */
|
|
.load-kopf {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
flex-wrap: wrap;
|
|
padding: 12px 14px;
|
|
background: var(--surface-sunk);
|
|
border-radius: var(--r-lg);
|
|
font-size: 13px;
|
|
}
|
|
.load-kopf-zahl strong {
|
|
font-size: 17px;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.load-kopf-warnung {
|
|
color: var(--red);
|
|
font-weight: 600;
|
|
}
|
|
.load-kopf-warnung::before {
|
|
content: '';
|
|
display: inline-block;
|
|
width: 7px; height: 7px;
|
|
border-radius: 50%;
|
|
background: var(--red);
|
|
margin-right: 7px;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.load-luecken {
|
|
padding: 12px 14px;
|
|
border: 1px dashed var(--line);
|
|
border-radius: var(--r-lg);
|
|
font-size: 12px;
|
|
}
|
|
.load-luecke {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
padding: 4px 0;
|
|
border-bottom: 1px solid var(--line-soft);
|
|
}
|
|
.load-luecke:last-child { border-bottom: none; }
|
|
.load-luecke b { color: var(--text-muted); font-weight: 500; white-space: nowrap; }
|
|
|
|
/* Summenzeile je Mensch: ein durchgehender Riegel statt der Projektsegmente.
|
|
Sie beantwortet die Frage, die die Rollenzeilen nicht beantworten können —
|
|
wer in drei Rollen arbeitet, hat trotzdem nur eine Arbeitswoche. */
|
|
.load-zeile-summe .load-wer-name { font-weight: 600; }
|
|
.load-seg-summe { border-right: none; }
|
|
|
|
/* Marke der vollen Kapazität im Band. Sie sitzt am rechten Rand, weil das
|
|
volle Band genau die Wochenkapazität ist; ein Riegel, der sie erreicht,
|
|
stößt sichtbar an. */
|
|
.load-kapazitaet {
|
|
position: absolute;
|
|
right: 0; top: 0; bottom: 0;
|
|
width: 2px;
|
|
background: var(--text-subtle);
|
|
opacity: .5;
|
|
}
|
|
.load-zeile-summe .load-band { position: relative; }
|
|
|
|
.load-einheit {
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
color: var(--text-subtle);
|
|
margin-left: 2px;
|
|
}
|
|
|
|
.load-legende-item em {
|
|
font-style: normal;
|
|
color: var(--text-subtle);
|
|
margin-left: 3px;
|
|
}
|
|
|
|
/* Legende der Projektfarben, nur für die tatsächlich vorkommenden Projekte.
|
|
Nötig, weil in schmalen Segmenten die Projektnummer keinen Platz findet. */
|
|
.load-legende {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 6px 16px;
|
|
padding-top: 14px;
|
|
border-top: 1px solid var(--line-soft);
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
}
|
|
.load-legende-item {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
.load-legende-item i {
|
|
width: 9px;
|
|
height: 9px;
|
|
border-radius: 2px;
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
@media (max-width: 720px) {
|
|
.load-wer { flex-basis: 128px; }
|
|
.load-zeile { gap: 10px; }
|
|
.load-wert { flex-basis: 52px; font-size: 16px; }
|
|
}
|
|
|
|
.load-row-title {
|
|
grid-column: 1 / -1;
|
|
width: 100%;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
margin: 0 0 2px 0;
|
|
padding-top: 4px;
|
|
color: var(--text-muted);
|
|
text-transform: uppercase;
|
|
letter-spacing: .04em;
|
|
}
|
|
|
|
.load-card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-xl);
|
|
padding: 20px 22px;
|
|
min-width: 0;
|
|
width: 100%;
|
|
box-shadow: var(--sh-2);
|
|
transition: box-shadow .2s var(--ease), transform .2s var(--ease);
|
|
}
|
|
|
|
.load-card:hover {
|
|
box-shadow: var(--sh-3);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.load-card h3 {
|
|
margin: 0 0 4px;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
letter-spacing: -0.01em;
|
|
line-height: 1.25;
|
|
}
|
|
|
|
.load-num {
|
|
font-size: 30px;
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
letter-spacing: -0.03em;
|
|
line-height: 1.1;
|
|
}
|
|
|
|
.load-detail {
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.load-detail div {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 4px 0;
|
|
border-bottom: 1px solid var(--line-soft);
|
|
}
|
|
|
|
.load-detail div:last-child { border-bottom: none; }
|
|
|
|
/* === Modal, Form-Elemente & Listen === */
|
|
.modal-bg {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0,0,0,.35);
|
|
-webkit-backdrop-filter: blur(6px);
|
|
backdrop-filter: blur(6px);
|
|
display: none;
|
|
align-items: center;
|
|
justify-content: center;
|
|
/* Über Menüband (.app-topbar, 140) UND Seitenleiste (.side-menu, 200):
|
|
Mit dem früheren Wert 100 blieb die Navigation neben einem offenen Dialog
|
|
sichtbar und anklickbar — auch hinter dem "blockierenden" Login-Fenster —
|
|
und die Seitenleiste verdeckte bei mittleren Fensterbreiten die linke
|
|
Kante breiter Dialoge. */
|
|
z-index: 300;
|
|
}
|
|
|
|
.modal {
|
|
background: var(--surface);
|
|
padding: 28px;
|
|
border-radius: var(--r-xl);
|
|
max-width: 500px;
|
|
width: 90%;
|
|
max-height: 85vh;
|
|
overflow: auto;
|
|
box-shadow: var(--sh-3);
|
|
}
|
|
|
|
.modal h2 {
|
|
margin-top: 0;
|
|
font-size: 20px;
|
|
font-weight: 600;
|
|
letter-spacing: -0.02em;
|
|
line-height: 1.15;
|
|
}
|
|
|
|
.form-row {
|
|
margin-bottom: 14px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 5px;
|
|
}
|
|
|
|
.form-row label {
|
|
font-weight: 500;
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
/* Gesperrte Bedienelemente: Ohne eigene :disabled-Regeln schlagen die
|
|
Farbangaben oben die Browser-Voreinstellung, und ein deaktivierter Knopf
|
|
sieht exakt aus wie ein bedienbarer (z. B. in gesperrten Sitzungen oder
|
|
bei der eigenen Zeile in der Benutzerverwaltung). */
|
|
button:disabled,
|
|
input:disabled,
|
|
select:disabled,
|
|
textarea:disabled {
|
|
opacity: .45;
|
|
cursor: not-allowed;
|
|
box-shadow: none;
|
|
}
|
|
button:disabled:hover,
|
|
select:disabled:hover {
|
|
filter: none;
|
|
transform: none;
|
|
}
|
|
|
|
/* Warnband unten rechts, wenn das Speichern fehlschlägt (showSyncProblem() in
|
|
js/store.js). Liegt über allem, damit es auch neben einem offenen Dialog
|
|
sichtbar bleibt. */
|
|
.sync-problem-bar {
|
|
position: fixed;
|
|
right: 18px;
|
|
bottom: 18px;
|
|
z-index: 500;
|
|
max-width: 340px;
|
|
background: var(--red);
|
|
color: #fff;
|
|
font-size: 13px;
|
|
line-height: 1.45;
|
|
padding: 11px 15px;
|
|
border-radius: var(--r-md);
|
|
box-shadow: var(--sh-3);
|
|
}
|
|
|
|
/* Hinweisband gesperrter Sitzungen (renderBoard()/renderWochenbericht()).
|
|
Vorher standen die Farben als Inline-Angaben im Markup und waren damit im
|
|
Dunkelmodus grell weiß-rosa. */
|
|
.lock-notice {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
background: var(--surface-alt);
|
|
border: 1px solid var(--red);
|
|
color: var(--red);
|
|
border-radius: var(--r-md);
|
|
padding: 9px 13px;
|
|
margin-bottom: 12px;
|
|
font-size: 13px;
|
|
}
|
|
|
|
/* === LP-Aufwand-Maske als Matrix-Tabelle ===
|
|
Zeilen = Leistungsphasen, Spalten = Rollen + "amtierend" (Radio). */
|
|
.lp-matrix {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin: 6px 0 12px;
|
|
}
|
|
.lp-matrix thead th {
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
color: var(--text-muted);
|
|
text-align: left;
|
|
padding: 4px 8px;
|
|
border-bottom: 1px solid var(--line-soft);
|
|
}
|
|
.lp-matrix th.lp-lph {
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
text-align: left;
|
|
padding: 6px 8px;
|
|
white-space: nowrap;
|
|
}
|
|
.lp-matrix td {
|
|
padding: 3px 8px;
|
|
}
|
|
.lp-matrix td input[type=text] {
|
|
width: 64px;
|
|
padding: 4px 7px;
|
|
font-size: 13px;
|
|
}
|
|
.lp-matrix tbody th.lp-lph,
|
|
.lp-matrix tbody td {
|
|
border-bottom: 1px solid var(--line-soft);
|
|
}
|
|
.lp-matrix tbody tr:last-child th.lp-lph,
|
|
.lp-matrix tbody tr:last-child td {
|
|
border-bottom: none;
|
|
}
|
|
.lp-matrix tbody tr:nth-child(even) th.lp-lph,
|
|
.lp-matrix tbody tr:nth-child(even) td {
|
|
background: var(--surface-alt);
|
|
}
|
|
|
|
/* LP-Aufwand- und Neues-Projekt-Modal etwas breiter für die Matrix-Tabelle */
|
|
#lpModal .modal,
|
|
#newProjModal .modal {
|
|
max-width: 640px;
|
|
}
|
|
|
|
/* .bubble-card: gemeinsame "Bubble-Card"-Optik für Projekt- und
|
|
Personen-Karten (Projekte-/Projektbeteiligte-Tab) — gleicher Radius/
|
|
Schatten wie .load-card (Dashboard/Auslastung), aber ohne Hover-Anhebung,
|
|
weil die Projekt-Karten Formularelemente enthalten (eine Anhebung beim
|
|
Überfahren wäre dort irritierend). */
|
|
.bubble-card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-xl);
|
|
padding: 20px 22px;
|
|
box-shadow: var(--sh-2);
|
|
transition: box-shadow .2s var(--ease);
|
|
}
|
|
|
|
.bubble-card:hover {
|
|
box-shadow: var(--sh-3);
|
|
}
|
|
|
|
.person-list-item,
|
|
.person-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
width: 100%;
|
|
}
|
|
|
|
.person-list-item {
|
|
align-items: center;
|
|
gap: 8px;
|
|
overflow-x: auto;
|
|
flex-direction: row;
|
|
}
|
|
|
|
.person-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 8px 4px;
|
|
cursor: pointer;
|
|
overflow-x: auto;
|
|
transition: background .2s var(--ease);
|
|
}
|
|
|
|
.person-header:hover { background: var(--surface-sunk); }
|
|
|
|
/* Schaltflächen-Optik auch für Links: Ein <a class="secondary"> in einer
|
|
Knopfleiste sah sonst aus wie ein blauer Standardlink, weil .secondary
|
|
ausschließlich für <button> definiert war. */
|
|
a.secondary {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
background: var(--surface-alt);
|
|
color: var(--text);
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--r-md);
|
|
padding: 6px 12px;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
text-decoration: none;
|
|
transition: background .15s var(--ease), border-color .15s var(--ease);
|
|
}
|
|
a.secondary:hover { background: var(--surface-sunk); border-color: var(--text-subtle); }
|
|
|
|
/* Trennzeichen in der Board-Legende (Rollenfarben | Art der Zuordnung) */
|
|
.legend-sep { color: var(--text-subtle); }
|
|
|
|
/* Zugehörigkeit zur aktuellen Sitzung + Schaltfläche rechts in der Zeile
|
|
(renderPeople(), js/people/people.js). */
|
|
.person-week-state {
|
|
margin-left: auto;
|
|
font-size: 12px;
|
|
color: var(--green);
|
|
white-space: nowrap;
|
|
}
|
|
.person-header button { flex: 0 0 auto; }
|
|
|
|
.person-toggle {
|
|
flex: 0 0 auto;
|
|
font-size: 11px;
|
|
color: var(--text-subtle);
|
|
width: 14px;
|
|
text-align: center;
|
|
}
|
|
|
|
.person-toggle .icon {
|
|
transition: transform .2s var(--ease);
|
|
}
|
|
|
|
.person-card.open .person-toggle .icon {
|
|
transform: rotate(90deg);
|
|
}
|
|
|
|
.person-list-item span.drag-handle,
|
|
.person-header span.drag-handle {
|
|
flex: 0 0 auto;
|
|
color: var(--text-subtle);
|
|
cursor: grab;
|
|
}
|
|
|
|
.person-list-item input[type=text],
|
|
.person-header input[type=text] { flex: 0 0 auto; max-width: 25vw; }
|
|
.person-list-item input[type=text]:nth-of-type(1),
|
|
.person-header input[type=text]:nth-of-type(1) { max-width: 80px; }
|
|
.person-list-item input[type=text]:nth-of-type(2),
|
|
.person-header input[type=text]:nth-of-type(2) { max-width: 180px; }
|
|
|
|
.person-list-item select,
|
|
.person-header select,
|
|
.person-header .role-checkboxes {
|
|
flex: 0 0 auto;
|
|
min-width: 110px;
|
|
}
|
|
|
|
.person-list-item button,
|
|
.person-header button {
|
|
flex: 0 0 auto;
|
|
margin-left: auto;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* .projects-sortable: Listen-Container je Projekt-Gruppe im Projekte-Tab
|
|
(projects.js) — eine Bubble-Card mit den Projekten als Listenzeilen
|
|
(kein Karten-Raster mehr, siehe .project-item). Der Container-Klassenname
|
|
bleibt unverändert, damit die Drag-and-Drop-Selektoren (handleDrop/
|
|
reorderProjects) weiter greifen. */
|
|
.projects-sortable {
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: var(--surface);
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-xl);
|
|
box-shadow: var(--sh-2);
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* .people-readonly: Listen-Container im Projektbeteiligte-Tab (people.js) —
|
|
eine Bubble-Card mit den Personen als Listenzeilen (kein Karten-Raster
|
|
mehr, siehe .person-card). */
|
|
.people-readonly {
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: var(--surface);
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-xl);
|
|
box-shadow: var(--sh-2);
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* .person-card: eine Listenzeile im Projektbeteiligte-Tab (ersetzt die
|
|
vormalige eigene Bubble-Card je Person) — der klickbare .person-header
|
|
bleibt die sichtbare Zeile, .person-absences klappt darunter auf. */
|
|
.person-card {
|
|
border-bottom: 1px solid var(--line-soft);
|
|
padding: 0 16px;
|
|
}
|
|
.person-card:last-child { border-bottom: none; }
|
|
|
|
/* .sidebar-nav-divider: trennt die Modul-Navigation von Einstellungen/Admin. */
|
|
.sidebar-nav-divider {
|
|
border: none;
|
|
border-top: 1px solid var(--line-soft);
|
|
margin: 8px 6px;
|
|
}
|
|
|
|
/* .project-item: eine Listenzeile im Projekte-Tab (ersetzt die vormalige
|
|
eigene Bubble-Card je Projekt) — überschreibt die Zeilen-Basis von
|
|
.person-list-item mit einer umbrechenden Zeile plus rechtsbündigen
|
|
Aktionen (.project-card-footer); das Karten-Attribut (data-project-id) und
|
|
die Drag-Handler bleiben unverändert. */
|
|
.project-item {
|
|
overflow-x: visible;
|
|
flex-wrap: wrap;
|
|
padding: 12px 16px;
|
|
border-bottom: 1px solid var(--line-soft);
|
|
border-radius: 0;
|
|
transition: background .15s var(--ease);
|
|
}
|
|
.project-item:last-child { border-bottom: none; }
|
|
.project-item:hover { background: var(--surface-sunk); }
|
|
|
|
.project-tags-row {
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
flex: 1 1 200px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.project-card-footer {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-left: auto;
|
|
}
|
|
/* Überschreibt die generische .person-list-item-button-Regel (margin-left:
|
|
auto auf jedem direkten Button), sonst würden sich die beiden Footer-
|
|
Buttons gegenseitig auseinanderschieben statt gruppiert zu bleiben — nur
|
|
der Footer als Ganzes soll rechtsbündig sein. */
|
|
.project-card-footer button { margin-left: 0; }
|
|
|
|
.tag-input { width: 110px; font-size: 12px; padding: 3px 8px; }
|
|
|
|
/* === Hinweise & Hilfsklassen === */
|
|
.legend {
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
margin: 10px 0;
|
|
display: flex;
|
|
gap: 16px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.legend span {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
}
|
|
|
|
|
|
/* === Anwesenheit der Sitzung ============================================
|
|
Fokus-Modus: ein aufklappbarer Abschnitt (.bericht-disclosure, siehe
|
|
weiter oben) am Seitenfuß des Wochenberichts, UNABHÄNGIG vom gewählten
|
|
Projekt (eine Sitzungs-, keine Projekt-Angabe) - anders als Leistungs-
|
|
phasen/Beteiligte, die seit Eriks Rückmeldung zum Entwurfsvergleich wieder
|
|
dauerhaft sichtbar sind (.bericht-meta-grid, siehe weiter oben), bleibt
|
|
dies der einzige Aufklapper. 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;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
padding: 12px 14px;
|
|
}
|
|
|
|
.attendance-tile {
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 4px;
|
|
width: 84px;
|
|
padding: 9px 5px 8px;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--r-md);
|
|
background: var(--surface);
|
|
color: var(--text-muted);
|
|
font-family: inherit;
|
|
font-weight: 400;
|
|
cursor: pointer;
|
|
transition: border-color .15s var(--ease), background .15s var(--ease), color .15s var(--ease);
|
|
}
|
|
.attendance-tile:hover { border-color: var(--text-subtle); background: var(--surface-alt); }
|
|
.attendance-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
|
|
.attendance-av {
|
|
width: 30px; height: 30px;
|
|
border-radius: 50%;
|
|
background: var(--surface-sunk);
|
|
color: var(--text-muted);
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-size: 11px; font-weight: 600;
|
|
transition: background .15s var(--ease), color .15s var(--ease);
|
|
}
|
|
.attendance-name {
|
|
font-size: 10.5px;
|
|
line-height: 1.25;
|
|
text-align: center;
|
|
word-break: break-word;
|
|
}
|
|
.attendance-note {
|
|
font-size: 9.5px;
|
|
color: var(--amber);
|
|
line-height: 1.2;
|
|
text-align: center;
|
|
}
|
|
/* Der Haken trägt den Zustand zusätzlich zur Farbe — nicht jede Person
|
|
unterscheidet Grün und Grau zuverlässig. */
|
|
.attendance-mark {
|
|
position: absolute;
|
|
top: 4px; right: 6px;
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
color: var(--green);
|
|
line-height: 1;
|
|
}
|
|
|
|
.attendance-tile.present {
|
|
border-color: var(--green);
|
|
background: var(--attend-bg);
|
|
color: var(--green);
|
|
font-weight: 500;
|
|
}
|
|
.attendance-tile.present:hover { background: var(--attend-bg-hover); border-color: var(--green); }
|
|
.attendance-tile.present .attendance-av { background: var(--green); color: #fff; }
|
|
|
|
.attendance-tile.locked-cell { cursor: not-allowed; opacity: .7; }
|
|
.attendance-tile.locked-cell:hover { border-color: var(--line); background: var(--surface); }
|
|
.attendance-tile.present.locked-cell:hover { background: var(--attend-bg); border-color: var(--green); }
|
|
|
|
/* Legende ist seit der Trennung reine Namenserklärung, nicht mehr klickbar */
|
|
|
|
|
|
.swatch {
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 4px;
|
|
display: inline-block;
|
|
box-shadow: inset 0 0 0 1px rgba(0,0,0,.06);
|
|
}
|
|
|
|
.table-wrap {
|
|
overflow: auto;
|
|
max-height: 75vh;
|
|
border-radius: var(--r-lg);
|
|
border: 1px solid var(--line-soft);
|
|
box-shadow: var(--sh-1);
|
|
background: var(--surface);
|
|
}
|
|
|
|
/* Fehlermeldung direkt am Formularfeld (Passwort-Dialog, Aufgaben-Dialog)
|
|
statt als alert() — der Fehler steht dort, wo er entstanden ist. */
|
|
.field-error { font-size: 11.5px; color: var(--red); }
|
|
|
|
.hint {
|
|
color: var(--text-subtle);
|
|
font-size: 12px;
|
|
margin: 8px 0;
|
|
}
|
|
|
|
.del-proj {
|
|
color: var(--red);
|
|
cursor: pointer;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.lock-notice button { margin-left: 12px; }
|
|
|
|
|
|
input[type=text].edit-name {
|
|
border: 1px solid transparent;
|
|
background: transparent;
|
|
font-weight: 600;
|
|
width: 100%;
|
|
font-size: 12px;
|
|
padding: 3px 5px;
|
|
border-radius: var(--r-sm);
|
|
transition: background .15s var(--ease), border-color .15s var(--ease), box-shadow .15s var(--ease);
|
|
}
|
|
|
|
input[type=text].edit-name:hover { background: rgba(0,0,0,.03); }
|
|
|
|
input[type=text].edit-name:focus {
|
|
background: var(--surface);
|
|
border-color: var(--accent);
|
|
box-shadow: 0 0 0 3px rgba(0,113,227,.18);
|
|
outline: none;
|
|
}
|
|
|
|
/* === Scrollbars (WebKit) === */
|
|
::-webkit-scrollbar { width: 10px; height: 10px; }
|
|
::-webkit-scrollbar-track { background: transparent; }
|
|
::-webkit-scrollbar-thumb {
|
|
background: rgba(0,0,0,.2);
|
|
border-radius: var(--r-pill);
|
|
border: 2px solid transparent;
|
|
background-clip: content-box;
|
|
}
|
|
::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,.32); background-clip: content-box; }
|
|
|
|
/* ============================================================
|
|
=== DARK MODE ===
|
|
Automatisch nach Systemeinstellung ODER manuell per
|
|
<html data-theme="dark"> auslösbar.
|
|
============================================================ */
|
|
|
|
@media (prefers-color-scheme: dark) {
|
|
html:not([data-theme="light"]) { color-scheme: dark; }
|
|
|
|
html:not([data-theme="light"]) {
|
|
/* 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: #e7eef3;
|
|
--text-muted: #9fb3c0;
|
|
--text-subtle: #70838f;
|
|
|
|
--bg: #10181f;
|
|
--surface: #141c23;
|
|
--surface-alt: #16222c;
|
|
--surface-sunk: #1c2730;
|
|
|
|
--line: #2c3e49;
|
|
--line-soft: #1f2b33;
|
|
|
|
--green: #4cd080;
|
|
--amber: #e8a15c;
|
|
--red: #ef6f63;
|
|
--red-hover: #f58a7f;
|
|
--red-active: #c65349;
|
|
|
|
--attend-bg: rgba(76,208,128,.14);
|
|
--attend-bg-hover: rgba(76,208,128,.22);
|
|
|
|
/* 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;
|
|
|
|
--sh-1: 0 1px 2px rgba(0,0,0,.5), 0 1px 3px rgba(0,0,0,.4);
|
|
--sh-2: 0 2px 10px rgba(0,0,0,.55), 0 1px 3px rgba(0,0,0,.4);
|
|
--sh-3: 0 12px 40px rgba(0,0,0,.7), 0 2px 10px rgba(0,0,0,.5);
|
|
}
|
|
}
|
|
|
|
html[data-theme="dark"] {
|
|
color-scheme: dark;
|
|
|
|
--accent: #7fc3dd;
|
|
--accent-hover: #96cfe3;
|
|
--accent-active: #5fa3c0;
|
|
--accent-soft: #16222c;
|
|
|
|
--text: #e7eef3;
|
|
--text-muted: #9fb3c0;
|
|
--text-subtle: #70838f;
|
|
|
|
--bg: #10181f;
|
|
--surface: #141c23;
|
|
--surface-alt: #16222c;
|
|
--surface-sunk: #1c2730;
|
|
|
|
--line: #2c3e49;
|
|
--line-soft: #1f2b33;
|
|
|
|
--green: #4cd080;
|
|
--amber: #e8a15c;
|
|
--red: #ef6f63;
|
|
--red-hover: #f58a7f;
|
|
--red-active: #c65349;
|
|
|
|
--attend-bg: rgba(76,208,128,.14);
|
|
--attend-bg-hover: rgba(76,208,128,.22);
|
|
|
|
--band-neu: #116896;
|
|
--band-fortschritt: #1f7a4f;
|
|
|
|
--protokoll-neu-bg: rgba(255,214,10,.14);
|
|
--protokoll-neu-border: #ffd60a;
|
|
|
|
--sh-1: 0 1px 2px rgba(0,0,0,.5), 0 1px 3px rgba(0,0,0,.4);
|
|
--sh-2: 0 2px 10px rgba(0,0,0,.55), 0 1px 3px rgba(0,0,0,.4);
|
|
--sh-3: 0 12px 40px rgba(0,0,0,.7), 0 2px 10px rgba(0,0,0,.5);
|
|
}
|
|
|
|
/* Gemeinsame Dark-Style-Regeln (System oder manuell) */
|
|
@media (prefers-color-scheme: dark) {
|
|
html:not([data-theme="light"]) .app-topbar {
|
|
background: rgba(28,28,30,.75);
|
|
border-bottom: 1px solid var(--line);
|
|
}
|
|
html:not([data-theme="light"]) .toolbar-divider { background: var(--line); }
|
|
html:not([data-theme="light"]) .sidebar-nav-divider { border-top-color: var(--line); }
|
|
html:not([data-theme="light"]) .side-menu {
|
|
background: var(--surface);
|
|
border-right-color: var(--line);
|
|
}
|
|
html:not([data-theme="light"]) .side-menu-mobile-header,
|
|
html:not([data-theme="light"]) .sidebar-collapse-bar {
|
|
border-color: var(--line);
|
|
}
|
|
html:not([data-theme="light"]) .user-menu-dropdown {
|
|
background: var(--surface);
|
|
border-color: var(--line);
|
|
}
|
|
html:not([data-theme="light"]) .user-menu-info { border-bottom-color: var(--line); }
|
|
html:not([data-theme="light"]) a.menu-item:hover,
|
|
html:not([data-theme="light"]) button.menu-item:hover { background: rgba(255,255,255,.08); }
|
|
html:not([data-theme="light"]) .sidebar-collapse-bar:hover { background: rgba(255,255,255,.08); }
|
|
html:not([data-theme="light"]) .theme-toggle-circle {
|
|
background: var(--surface-sunk);
|
|
border-color: var(--line);
|
|
color: var(--text);
|
|
}
|
|
html:not([data-theme="light"]) .menu-corner-btn {
|
|
background: var(--surface-sunk);
|
|
border-color: var(--line);
|
|
color: var(--text);
|
|
}
|
|
html:not([data-theme="light"]) .menu-corner-btn:hover { background: var(--line); }
|
|
html:not([data-theme="light"]) .topbar-logo { filter: brightness(.92); }
|
|
html:not([data-theme="light"]) .nav-item:hover { background: rgba(255,255,255,.08); }
|
|
html:not([data-theme="light"]) .nav-item.active { background: var(--accent-soft); }
|
|
|
|
html:not([data-theme="light"]) button:focus-visible { box-shadow: 0 0 0 3px rgba(10,132,255,.45); }
|
|
html:not([data-theme="light"]) button.secondary { background: rgba(255,255,255,.10); color: var(--text); }
|
|
html:not([data-theme="light"]) button.secondary:hover { background: rgba(255,255,255,.15); }
|
|
html:not([data-theme="light"]) button.secondary:active { background: rgba(255,255,255,.20); }
|
|
html:not([data-theme="light"]) button.danger { background: #d70015; }
|
|
html:not([data-theme="light"]) button.danger:hover { background: var(--red-hover); }
|
|
html:not([data-theme="light"]) button.danger:active { background: var(--red-active); }
|
|
|
|
html:not([data-theme="light"]) select,
|
|
html:not([data-theme="light"]) input[type=text],
|
|
html:not([data-theme="light"]) input[type=email],
|
|
html:not([data-theme="light"]) input[type=date] {
|
|
background-color: var(--surface-sunk);
|
|
border-color: var(--line);
|
|
color: var(--text);
|
|
}
|
|
html:not([data-theme="light"]) select:focus,
|
|
html:not([data-theme="light"]) input[type=text]:focus,
|
|
html:not([data-theme="light"]) input[type=email]:focus,
|
|
html:not([data-theme="light"]) input[type=date]:focus {
|
|
box-shadow: 0 0 0 3px rgba(10,132,255,.28);
|
|
}
|
|
html:not([data-theme="light"]) select {
|
|
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath fill='%23a1a1a6' d='M0 0h10L5 6z'/%3E%3C/svg%3E");
|
|
}
|
|
html:not([data-theme="light"]) ::placeholder { color: var(--text-subtle); }
|
|
|
|
html:not([data-theme="light"]) .status-edit {
|
|
background: var(--surface-sunk);
|
|
border-color: var(--line);
|
|
}
|
|
html:not([data-theme="light"]) .status-edit:focus { box-shadow: 0 0 0 3px rgba(10,132,255,.28); }
|
|
html:not([data-theme="light"]) .status-edit.status-new { color: var(--green); }
|
|
html:not([data-theme="light"]) .status-toolbar {
|
|
background: rgba(44,44,46,.9);
|
|
border-color: var(--line);
|
|
}
|
|
html:not([data-theme="light"]) .status-toolbar button:hover { background: rgba(255,255,255,.10); }
|
|
html:not([data-theme="light"]) .status-toolbar button:active { background: rgba(255,255,255,.16); }
|
|
|
|
html:not([data-theme="light"]) .locked-cell { background: #38383a !important; opacity: .55; }
|
|
html:not([data-theme="light"]) .locked-cell input {
|
|
background: #38383a !important;
|
|
border-color: var(--line) !important;
|
|
color: var(--text-muted) !important;
|
|
}
|
|
|
|
html:not([data-theme="light"]) .bar { background: #38383a; }
|
|
html:not([data-theme="light"]) .board-avatar { border-color: var(--line); }
|
|
|
|
html:not([data-theme="light"]) .load-card { border-color: var(--line); }
|
|
html:not([data-theme="light"]) .bubble-card,
|
|
html:not([data-theme="light"]) .projects-sortable,
|
|
html:not([data-theme="light"]) .people-readonly,
|
|
html:not([data-theme="light"]) .board-list { border-color: var(--line); }
|
|
html:not([data-theme="light"]) .modal-bg { background: rgba(0,0,0,.6); }
|
|
html:not([data-theme="light"]) .modal { border: 1px solid var(--line); }
|
|
|
|
html:not([data-theme="light"]) .swatch { box-shadow: inset 0 0 0 1px rgba(255,255,255,.14); }
|
|
html:not([data-theme="light"]) .table-wrap { border-color: var(--line); }
|
|
html:not([data-theme="light"]) .del-proj { color: var(--red); }
|
|
html:not([data-theme="light"]) input[type=text].edit-name { border-color: transparent; background: transparent; }
|
|
html:not([data-theme="light"]) input[type=text].edit-name:hover { background: rgba(255,255,255,.06); }
|
|
html:not([data-theme="light"]) input[type=text].edit-name:focus {
|
|
background: var(--surface-sunk);
|
|
border-color: var(--accent);
|
|
box-shadow: 0 0 0 3px rgba(10,132,255,.28);
|
|
}
|
|
|
|
html:not([data-theme="light"]) ::-webkit-scrollbar-thumb { background: rgba(255,255,255,.22); background-clip: content-box; }
|
|
html:not([data-theme="light"]) ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,.34); background-clip: content-box; }
|
|
}
|
|
|
|
html[data-theme="dark"] .app-topbar {
|
|
background: rgba(28,28,30,.75);
|
|
border-bottom: 1px solid var(--line);
|
|
}
|
|
html[data-theme="dark"] .toolbar-divider { background: var(--line); }
|
|
html[data-theme="dark"] .sidebar-nav-divider { border-top-color: var(--line); }
|
|
html[data-theme="dark"] .side-menu {
|
|
background: var(--surface);
|
|
border-right-color: var(--line);
|
|
}
|
|
html[data-theme="dark"] .side-menu-mobile-header,
|
|
html[data-theme="dark"] .sidebar-collapse-bar {
|
|
border-color: var(--line);
|
|
}
|
|
html[data-theme="dark"] .user-menu-dropdown {
|
|
background: var(--surface);
|
|
border-color: var(--line);
|
|
}
|
|
html[data-theme="dark"] .user-menu-info { border-bottom-color: var(--line); }
|
|
html[data-theme="dark"] a.menu-item:hover,
|
|
html[data-theme="dark"] button.menu-item:hover { background: rgba(255,255,255,.08); }
|
|
html[data-theme="dark"] .sidebar-collapse-bar:hover { background: rgba(255,255,255,.08); }
|
|
html[data-theme="dark"] .theme-toggle-circle {
|
|
background: var(--surface-sunk);
|
|
border-color: var(--line);
|
|
color: var(--text);
|
|
}
|
|
html[data-theme="dark"] .menu-corner-btn {
|
|
background: var(--surface-sunk);
|
|
border-color: var(--line);
|
|
color: var(--text);
|
|
}
|
|
html[data-theme="dark"] .menu-corner-btn:hover { background: var(--line); }
|
|
html[data-theme="dark"] .topbar-logo { filter: brightness(.92); }
|
|
html[data-theme="dark"] .nav-item:hover { background: rgba(255,255,255,.08); }
|
|
html[data-theme="dark"] .nav-item.active { background: var(--accent-soft); }
|
|
|
|
html[data-theme="dark"] button:focus-visible { box-shadow: 0 0 0 3px rgba(10,132,255,.45); }
|
|
html[data-theme="dark"] button.secondary { background: rgba(255,255,255,.10); color: var(--text); }
|
|
html[data-theme="dark"] button.secondary:hover { background: rgba(255,255,255,.15); }
|
|
html[data-theme="dark"] button.secondary:active { background: rgba(255,255,255,.20); }
|
|
html[data-theme="dark"] button.danger { background: #d70015; }
|
|
html[data-theme="dark"] button.danger:hover { background: var(--red-hover); }
|
|
html[data-theme="dark"] button.danger:active { background: var(--red-active); }
|
|
|
|
html[data-theme="dark"] select,
|
|
html[data-theme="dark"] input[type=text],
|
|
html[data-theme="dark"] input[type=date] {
|
|
background-color: var(--surface-sunk);
|
|
border-color: var(--line);
|
|
color: var(--text);
|
|
}
|
|
html[data-theme="dark"] select:focus,
|
|
html[data-theme="dark"] input[type=text]:focus,
|
|
html[data-theme="dark"] input[type=date]:focus {
|
|
box-shadow: 0 0 0 3px rgba(10,132,255,.28);
|
|
}
|
|
html[data-theme="dark"] select {
|
|
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath fill='%23a1a1a6' d='M0 0h10L5 6z'/%3E%3C/svg%3E");
|
|
}
|
|
html[data-theme="dark"] ::placeholder { color: var(--text-subtle); }
|
|
|
|
html[data-theme="dark"] .status-edit {
|
|
background: var(--surface-sunk);
|
|
border-color: var(--line);
|
|
}
|
|
html[data-theme="dark"] .status-edit:focus { box-shadow: 0 0 0 3px rgba(10,132,255,.28); }
|
|
html[data-theme="dark"] .status-edit.status-new { color: var(--green); }
|
|
html[data-theme="dark"] .status-toolbar {
|
|
background: rgba(44,44,46,.9);
|
|
border-color: var(--line);
|
|
}
|
|
html[data-theme="dark"] .status-toolbar button:hover { background: rgba(255,255,255,.10); }
|
|
html[data-theme="dark"] .status-toolbar button:active { background: rgba(255,255,255,.16); }
|
|
|
|
html[data-theme="dark"] .locked-cell { background: #38383a !important; opacity: .55; }
|
|
html[data-theme="dark"] .locked-cell input {
|
|
background: #38383a !important;
|
|
border-color: var(--line) !important;
|
|
color: var(--text-muted) !important;
|
|
}
|
|
|
|
html[data-theme="dark"] .bar { background: #38383a; }
|
|
html[data-theme="dark"] .board-avatar { border-color: var(--line); }
|
|
|
|
html[data-theme="dark"] .load-card { border-color: var(--line); }
|
|
html[data-theme="dark"] .bubble-card,
|
|
html[data-theme="dark"] .projects-sortable,
|
|
html[data-theme="dark"] .people-readonly,
|
|
html[data-theme="dark"] .board-list { border-color: var(--line); }
|
|
html[data-theme="dark"] .modal-bg { background: rgba(0,0,0,.6); }
|
|
html[data-theme="dark"] .modal { border: 1px solid var(--line); }
|
|
|
|
html[data-theme="dark"] .swatch { box-shadow: inset 0 0 0 1px rgba(255,255,255,.14); }
|
|
html[data-theme="dark"] .table-wrap { border-color: var(--line); }
|
|
html[data-theme="dark"] .del-proj { color: var(--red); }
|
|
html[data-theme="dark"] input[type=text].edit-name { border-color: transparent; background: transparent; }
|
|
html[data-theme="dark"] input[type=text].edit-name:hover { background: rgba(255,255,255,.06); }
|
|
html[data-theme="dark"] input[type=text].edit-name:focus {
|
|
background: var(--surface-sunk);
|
|
border-color: var(--accent);
|
|
box-shadow: 0 0 0 3px rgba(10,132,255,.28);
|
|
}
|
|
|
|
html[data-theme="dark"] ::-webkit-scrollbar-thumb { background: rgba(255,255,255,.22); background-clip: content-box; }
|
|
html[data-theme="dark"] ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,.34); background-clip: content-box; }
|
|
|
|
/* === GANTT / Projektplan === */
|
|
.modal-wide {
|
|
max-width: 900px;
|
|
width: 92%;
|
|
}
|
|
|
|
.gantt-container {
|
|
background: var(--surface);
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-lg);
|
|
padding: 12px 0;
|
|
box-shadow: var(--sh-1);
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
overflow-x: auto;
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
|
|
.gantt-chart {
|
|
width: 100%;
|
|
min-width: 0;
|
|
position: relative;
|
|
}
|
|
|
|
.gantt-row {
|
|
display: flex;
|
|
align-items: center;
|
|
position: relative;
|
|
height: 32px;
|
|
border-bottom: 1px solid var(--line-soft);
|
|
}
|
|
|
|
.gantt-row.gantt-header,
|
|
.gantt-row.gantt-days {
|
|
height: 26px;
|
|
border-bottom: 1px solid var(--line);
|
|
}
|
|
|
|
.gantt-timeline {
|
|
position: relative;
|
|
flex: 1 1 auto;
|
|
height: 100%;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.gantt-row.gantt-project {
|
|
height: 34px;
|
|
}
|
|
|
|
.gantt-row.gantt-project.highlighted {
|
|
background: var(--accent-soft);
|
|
}
|
|
|
|
.gantt-row.gantt-vacation {
|
|
height: 22px;
|
|
background: transparent;
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.gantt-label {
|
|
width: 220px;
|
|
flex: 0 0 220px;
|
|
min-width: 220px;
|
|
max-width: 220px;
|
|
height: 100%;
|
|
align-self: stretch;
|
|
box-sizing: border-box;
|
|
padding: 0 12px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
font-weight: 500;
|
|
font-size: 12px;
|
|
border-right: 1px solid var(--line-soft);
|
|
position: sticky;
|
|
left: 0;
|
|
z-index: 10;
|
|
background: var(--surface);
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.gantt-row.gantt-header .gantt-label,
|
|
.gantt-row.gantt-days .gantt-label {
|
|
background: var(--surface);
|
|
z-index: 15;
|
|
}
|
|
|
|
.gantt-row.gantt-project.highlighted .gantt-label {
|
|
background: var(--accent-soft);
|
|
}
|
|
|
|
.gantt-row.gantt-vacation .gantt-label {
|
|
font-weight: normal;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
background: var(--surface);
|
|
}
|
|
|
|
.gantt-row.gantt-todo .gantt-label {
|
|
background: var(--surface);
|
|
}
|
|
|
|
.gantt-vacation-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background: var(--amber);
|
|
}
|
|
|
|
.gantt-month,
|
|
.gantt-day {
|
|
position: absolute;
|
|
top: 0;
|
|
bottom: 0;
|
|
border-right: 1px solid var(--line-soft);
|
|
font-size: 10px;
|
|
color: var(--text-muted);
|
|
padding: 2px 4px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.gantt-month {
|
|
background: var(--surface-sunk);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.gantt-day {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.gantt-day.weekend {
|
|
background: var(--surface-sunk);
|
|
opacity: .7;
|
|
}
|
|
|
|
.gantt-day.gantt-week {
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
border-right: 1px solid var(--line);
|
|
}
|
|
|
|
/* Raster-Overlay: liegt über die gesamte Timeline-Breite (rechts der Labels).
|
|
Wochenend-Flächen zeichnen ohne z-index (Zeilen liegen darüber, sind aber
|
|
transparent); die Heute-Linie bekommt z-index, damit sie über den Balken,
|
|
aber unter den sticky Labels liegt. */
|
|
.gantt-grid-overlay {
|
|
position: absolute;
|
|
top: 0;
|
|
bottom: 0;
|
|
left: 220px;
|
|
right: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.gantt-grid-weekend {
|
|
position: absolute;
|
|
top: 0;
|
|
bottom: 0;
|
|
background: var(--surface-sunk);
|
|
opacity: .55;
|
|
}
|
|
|
|
.gantt-grid-today {
|
|
position: absolute;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 0;
|
|
border-left: 2px solid var(--red);
|
|
opacity: .7;
|
|
z-index: 9;
|
|
}
|
|
|
|
.gantt-bar {
|
|
position: absolute;
|
|
top: 6px;
|
|
bottom: 6px;
|
|
border-radius: var(--r-sm);
|
|
overflow: hidden;
|
|
box-shadow: var(--sh-1);
|
|
}
|
|
|
|
.gantt-bar.project {
|
|
color: #fff;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.gantt-bar .gantt-progress {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
bottom: 0;
|
|
background: rgba(0,0,0,.22);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.gantt-bar-label {
|
|
position: relative;
|
|
z-index: 1;
|
|
padding: 0 8px;
|
|
font-size: 11px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.gantt-bar.vacation {
|
|
top: 4px;
|
|
bottom: 4px;
|
|
background: repeating-linear-gradient(
|
|
45deg,
|
|
rgba(255, 159, 10, .55),
|
|
rgba(255, 159, 10, .55) 6px,
|
|
rgba(255, 159, 10, .35) 6px,
|
|
rgba(255, 159, 10, .35) 12px
|
|
);
|
|
border: 1px solid var(--amber);
|
|
border-radius: var(--r-pill);
|
|
}
|
|
|
|
.gantt-row.gantt-todo { height: 26px; font-size: 11px; }
|
|
.gantt-row.gantt-todo.done { opacity: .55; }
|
|
.gantt-row.gantt-milestone-row { height: 40px; font-size: 11px; background: transparent; }
|
|
.gantt-row.gantt-milestone-row .gantt-milestone { top: 6px; }
|
|
.gantt-row.gantt-milestone-row .gantt-milestone-label { top: 24px; max-width: 140px; overflow: hidden; text-overflow: ellipsis; }
|
|
.gantt-row.gantt-todo-add {
|
|
height: 34px;
|
|
font-size: 12px;
|
|
background: var(--surface-alt);
|
|
position: relative;
|
|
}
|
|
.gantt-todo-add-sticky {
|
|
position: sticky;
|
|
left: 0;
|
|
z-index: 10;
|
|
display: flex;
|
|
align-items: center;
|
|
background: var(--surface-alt);
|
|
border-right: 1px solid var(--line-soft);
|
|
height: 100%;
|
|
align-self: stretch;
|
|
box-sizing: border-box;
|
|
}
|
|
.gantt-todo-add-sticky .gantt-label {
|
|
position: static;
|
|
border-right: none;
|
|
background: transparent;
|
|
flex: 0 0 220px;
|
|
width: 220px;
|
|
min-width: 220px;
|
|
max-width: 220px;
|
|
box-sizing: border-box;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 0 12px;
|
|
}
|
|
.gantt-bar.todo {
|
|
top: 5px;
|
|
bottom: 5px;
|
|
background: var(--accent);
|
|
border-radius: var(--r-sm);
|
|
}
|
|
.gantt-bar.todo.done {
|
|
background: var(--green);
|
|
}
|
|
.gantt-bar.todo.critical {
|
|
background: var(--red);
|
|
box-shadow: 0 0 0 2px rgba(215,0,21,.25);
|
|
}
|
|
.gantt-todo-add-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
flex-wrap: nowrap;
|
|
width: fit-content;
|
|
max-width: 100%;
|
|
flex-shrink: 0;
|
|
padding-right: 8px;
|
|
}
|
|
.gantt-todo-add input[type=text],
|
|
.gantt-todo-add input[type=date] {
|
|
padding: 4px 7px;
|
|
font-size: 12px;
|
|
}
|
|
.gantt-todo-add input[type=text] {
|
|
flex: 0 0 180px;
|
|
width: 180px;
|
|
min-width: 140px;
|
|
}
|
|
.gantt-todo-add input[type=date] {
|
|
flex: 0 0 120px;
|
|
width: 120px;
|
|
min-width: 120px;
|
|
}
|
|
.gantt-todo-add button {
|
|
flex: 0 0 auto;
|
|
padding: 4px 12px;
|
|
font-size: 12px;
|
|
}
|
|
.gantt-todo-add .gantt-timeline {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 0 8px;
|
|
overflow: hidden;
|
|
}
|
|
.gantt-todo-actions {
|
|
display: inline-flex;
|
|
gap: 4px;
|
|
margin-left: 6px;
|
|
}
|
|
|
|
/* === GANTT Abhängigkeiten (Dependencies) === */
|
|
.gantt-dependencies-svg {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
pointer-events: none;
|
|
z-index: 8;
|
|
overflow: visible;
|
|
}
|
|
|
|
.gantt-dep-path {
|
|
fill: none;
|
|
stroke: var(--accent);
|
|
stroke-width: 2.2px;
|
|
stroke-linecap: round;
|
|
stroke-linejoin: round;
|
|
opacity: 0.85;
|
|
transition: stroke 0.2s, stroke-width 0.2s, opacity 0.2s;
|
|
}
|
|
|
|
.gantt-dep-path.conflict {
|
|
stroke: var(--red);
|
|
stroke-dasharray: 5 3;
|
|
}
|
|
|
|
.gantt-dep-path.done {
|
|
stroke: var(--green);
|
|
}
|
|
|
|
.gantt-dep-path.highlight {
|
|
stroke-width: 3.5px;
|
|
opacity: 1;
|
|
filter: drop-shadow(0 0 3px rgba(0, 113, 227, 0.4));
|
|
}
|
|
|
|
.gantt-dep-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 3px;
|
|
font-size: 10px;
|
|
padding: 1px 6px;
|
|
border-radius: var(--r-pill);
|
|
background: var(--accent-soft);
|
|
color: var(--accent);
|
|
margin-left: 6px;
|
|
max-width: 100px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
vertical-align: middle;
|
|
border: 1px solid rgba(0, 113, 227, 0.2);
|
|
}
|
|
|
|
.gantt-dep-badge.conflict {
|
|
background: rgba(215, 0, 21, 0.1);
|
|
color: var(--red);
|
|
border-color: rgba(215, 0, 21, 0.2);
|
|
}
|
|
.gantt-todo-actions button {
|
|
padding: 2px 7px;
|
|
font-size: 10px;
|
|
}
|
|
|
|
.gantt-milestone {
|
|
position: absolute;
|
|
top: 2px;
|
|
width: 0;
|
|
height: 0;
|
|
border-left: 7px solid transparent;
|
|
border-right: 7px solid transparent;
|
|
border-bottom: 12px solid var(--red);
|
|
transform: translateX(-50%);
|
|
z-index: 2;
|
|
cursor: grab;
|
|
}
|
|
.gantt-milestone:active { cursor: grabbing; }
|
|
|
|
.gantt-milestone-label {
|
|
position: absolute;
|
|
top: 16px;
|
|
transform: translateX(-50%);
|
|
font-size: 10px;
|
|
color: var(--text-muted);
|
|
white-space: nowrap;
|
|
pointer-events: none;
|
|
z-index: 2;
|
|
}
|
|
|
|
.gantt-bar.project,
|
|
.gantt-bar.vacation { cursor: grab; }
|
|
.gantt-bar.project:active,
|
|
.gantt-bar.vacation:active { cursor: grabbing; }
|
|
|
|
.gantt-resize {
|
|
position: absolute;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 10px;
|
|
cursor: col-resize;
|
|
z-index: 3;
|
|
}
|
|
.gantt-resize-left { left: 0; }
|
|
.gantt-resize-right { right: 0; }
|
|
.gantt-resize:hover { background: rgba(255,255,255,.25); }
|
|
|
|
.gantt-form {
|
|
display: grid;
|
|
gap: 12px;
|
|
}
|
|
|
|
.gantt-form-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.gantt-form-row input[type=date] { flex: 1 1 140px; }
|
|
.gantt-form-row input[type=text] { flex: 2 1 200px; }
|
|
|
|
.gantt-modal-preview .gantt-chart {
|
|
min-width: auto;
|
|
}
|
|
|
|
/* === Datumsbereich-Picker (Von/Bis in einem Kalenderfenster) === */
|
|
.range-picker {
|
|
position: fixed;
|
|
z-index: 1100;
|
|
width: 288px;
|
|
background: var(--surface);
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--r-lg);
|
|
box-shadow: var(--sh-3);
|
|
padding: 10px;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.range-picker-head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.range-picker-title {
|
|
font-weight: 600;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.range-picker-hint {
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
text-align: center;
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.range-picker-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(7, 1fr);
|
|
gap: 2px;
|
|
}
|
|
|
|
.range-picker-weekday {
|
|
text-align: center;
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
color: var(--text-muted);
|
|
padding: 2px 0;
|
|
}
|
|
|
|
.range-picker-day {
|
|
text-align: center;
|
|
padding: 5px 0;
|
|
border-radius: var(--r-sm);
|
|
cursor: pointer;
|
|
user-select: none;
|
|
transition: background .15s var(--ease);
|
|
}
|
|
|
|
.range-picker-day:hover { background: var(--surface-sunk); }
|
|
.range-picker-day.weekend { color: var(--text-subtle); }
|
|
.range-picker-day.today { outline: 1px solid var(--accent); outline-offset: -1px; }
|
|
.range-picker-day.in-range { background: var(--accent-soft); border-radius: 0; }
|
|
.range-picker-day.sel-start,
|
|
.range-picker-day.sel-end {
|
|
background: var(--accent);
|
|
color: #fff;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.range-picker-footer {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
margin-top: 8px;
|
|
}
|
|
|
|
/* Anzeige-Feld, das den Picker öffnet */
|
|
.range-picker-display {
|
|
cursor: pointer;
|
|
background: var(--surface);
|
|
min-width: 170px;
|
|
}
|
|
|
|
.person-absences {
|
|
padding: 8px 8px 8px 40px;
|
|
margin-top: 4px;
|
|
border-radius: var(--r-md);
|
|
background: var(--surface-alt);
|
|
}
|
|
|
|
.absence-list {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.absence-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
background: var(--surface);
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-pill);
|
|
padding: 3px 10px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.absence-chip small {
|
|
color: var(--text-muted);
|
|
font-size: 11px;
|
|
}
|
|
|
|
.absence-form {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.absence-form input[type=date] { width: 130px; }
|
|
.absence-form select { min-width: 110px; }
|
|
|
|
/* Tag-Chips: editierbar in der Projekte-Liste (projects.js), read-only auf
|
|
Board-Zeilen, Dashboard-Aufgabenkarten und der Projektplan-Kopfzeile.
|
|
Farben ausschließlich über CSS-Variablen, die in beiden Dark-Mode-Blöcken
|
|
bereits passend umdefiniert sind — keine zusätzlichen Dark-Overrides nötig. */
|
|
.tag-chip-list {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 4px;
|
|
}
|
|
|
|
.tag-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
background: var(--surface-sunk);
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-pill);
|
|
padding: 2px 8px;
|
|
font-size: 11px;
|
|
font-weight: 400;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.tag-chip .tag-remove {
|
|
cursor: pointer;
|
|
font-weight: 600;
|
|
color: var(--text-subtle);
|
|
line-height: 1;
|
|
transition: color .15s var(--ease);
|
|
}
|
|
|
|
.tag-chip .tag-remove:hover { color: var(--red); }
|
|
|
|
/* === Dashboard ("Meine Aufgaben"-Tab, js/dashboard/dashboard.js) ===
|
|
Aufbau: Kopf — Band — zwei Zonen. Die frühere Anordnung stellte die
|
|
Uhrzeit als größte Zahl heraus, zeigte vier gleich große Kennzahlkacheln
|
|
(meist drei davon mit einer Null) und legte alles untereinander, sodass
|
|
der Urlaub eines Kollegen gleichrangig unter den eigenen offenen Aufgaben
|
|
stand. */
|
|
|
|
/* ---------- Kopf: Begrüßung links, Nebensachen rechts ---------- */
|
|
.dash-kopf {
|
|
display: flex;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
gap: 20px;
|
|
flex-wrap: wrap;
|
|
margin-bottom: 20px;
|
|
}
|
|
.dash-gruss {
|
|
margin: 0;
|
|
font-size: 22px;
|
|
font-weight: 600;
|
|
letter-spacing: -.02em;
|
|
}
|
|
.dash-gruss em {
|
|
font-style: normal;
|
|
font-weight: 400;
|
|
color: var(--text-muted);
|
|
}
|
|
.dash-kopf-rechts {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 12px;
|
|
}
|
|
.dash-sitzung {
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
padding: 2px 9px;
|
|
border-radius: var(--r-pill);
|
|
background: var(--surface-sunk);
|
|
color: var(--text-muted);
|
|
}
|
|
.dash-uhr {
|
|
font-size: 13px;
|
|
color: var(--text-muted);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
/* ---------- Band: der Befund als Satz ---------- */
|
|
.dash-band {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 22px;
|
|
flex-wrap: wrap;
|
|
padding: 16px 20px;
|
|
margin-bottom: 24px;
|
|
background: var(--surface-sunk);
|
|
border-radius: var(--r-lg);
|
|
}
|
|
/* Ohne Befund bleibt das Band farblos — eine Null in Alarmfarbe behauptet
|
|
ein Problem, das es nicht gibt. */
|
|
.dash-band.ist-ruhig {
|
|
background: transparent;
|
|
border: 1px solid var(--line-soft);
|
|
}
|
|
.dash-befund {
|
|
margin: 0;
|
|
flex: 1 1 300px;
|
|
min-width: 0;
|
|
font-size: 17px;
|
|
}
|
|
/* Die Zahl größer als der Satz: Sie ist die Nachricht, und ab 19 px fett
|
|
gilt für den Kontrast die Schwelle für große Schrift — die Ampelfarbe
|
|
erreicht auf der abgesetzten Bandfläche sonst nur 4,1:1 im dunklen Thema. */
|
|
.dash-befund b {
|
|
font-size: 1.15em;
|
|
font-weight: 600;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.dash-befund b.ist-rot { color: var(--red); }
|
|
.dash-befund b.ist-ruhig { color: var(--green); }
|
|
|
|
.dash-band-zahlen { display: flex; gap: 20px; flex-wrap: wrap; }
|
|
.dash-zahl { display: flex; flex-direction: column; gap: 1px; min-width: 74px; }
|
|
.dash-zahl b {
|
|
font-size: 19px;
|
|
font-weight: 600;
|
|
line-height: 1.15;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.dash-zahl span { font-size: 11px; color: var(--text-subtle); white-space: nowrap; }
|
|
.dash-zahl.ist-null b { color: var(--text-subtle); }
|
|
|
|
/* ---------- Zwei Zonen: links der Arbeitsstrom, rechts der Rand ---------- */
|
|
.dash-zonen {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr) 300px;
|
|
gap: 24px;
|
|
align-items: start;
|
|
}
|
|
|
|
.dash-abschnitt { margin-bottom: 24px; }
|
|
.dash-abschnitt:last-child { margin-bottom: 0; }
|
|
.dash-abschnitt-kopf {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 10px;
|
|
margin-bottom: 8px;
|
|
}
|
|
.dash-abschnitt-titel {
|
|
margin: 0;
|
|
font-size: 11.5px;
|
|
font-weight: 600;
|
|
letter-spacing: .06em;
|
|
text-transform: uppercase;
|
|
color: var(--text-muted);
|
|
}
|
|
.dash-abschnitt-zahl {
|
|
font-size: 11px;
|
|
padding: 1px 7px;
|
|
border-radius: var(--r-pill);
|
|
background: var(--surface-sunk);
|
|
color: var(--text-muted);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.dash-abschnitt-zusatz {
|
|
margin-left: auto;
|
|
font-size: 11px;
|
|
color: var(--text-subtle);
|
|
}
|
|
|
|
.dash-karte {
|
|
background: var(--surface);
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-lg);
|
|
box-shadow: var(--sh-1);
|
|
overflow: hidden;
|
|
}
|
|
.dash-leer { padding: 16px 18px; font-size: 13px; color: var(--text-subtle); }
|
|
|
|
/* ---------- Meine Aufgaben: Zeilen statt halbseitiger Karten ---------- */
|
|
.dash-auf {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 14px;
|
|
padding: 11px 16px;
|
|
border-bottom: 1px solid var(--line-soft);
|
|
text-decoration: none;
|
|
color: inherit;
|
|
transition: background .12s var(--ease);
|
|
}
|
|
.dash-auf:last-child { border-bottom: none; }
|
|
.dash-auf:hover { background: var(--surface-sunk); }
|
|
.dash-auf:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
|
|
|
|
.dash-auf-text { flex: 1 1 auto; min-width: 0; }
|
|
.dash-auf-titel {
|
|
display: block;
|
|
font-size: 13.5px;
|
|
font-weight: 500;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.dash-auf-meta {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 7px;
|
|
flex-wrap: wrap;
|
|
margin-top: 1px;
|
|
font-size: 11px;
|
|
color: var(--text-subtle);
|
|
}
|
|
.dash-auf-tag {
|
|
font-size: 10px;
|
|
padding: 0 6px;
|
|
border-radius: var(--r-sm);
|
|
background: var(--surface-sunk);
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.dash-auf-fortschritt { flex: 0 0 96px; display: flex; align-items: center; gap: 8px; }
|
|
.dash-auf-bar {
|
|
flex: 1;
|
|
height: 5px;
|
|
background: var(--surface-sunk);
|
|
border-radius: var(--r-pill);
|
|
overflow: hidden;
|
|
}
|
|
.dash-auf-bar i { display: block; height: 100%; background: var(--accent); border-radius: var(--r-pill); }
|
|
.dash-auf-pct {
|
|
min-width: 30px;
|
|
text-align: right;
|
|
font-size: 11px;
|
|
color: var(--text-subtle);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
.dash-auf-frist {
|
|
flex: 0 0 118px;
|
|
text-align: right;
|
|
font-size: 12px;
|
|
line-height: 1.3;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.dash-auf-frist em { display: block; font-style: normal; font-size: 10.5px; color: var(--text-subtle); }
|
|
.dash-auf-frist.ist-ueberfaellig, .dash-auf-frist.ist-ueberfaellig em { color: var(--red); }
|
|
.dash-auf-frist.ist-bald { color: var(--amber); }
|
|
|
|
/* ---------- Rechte Zone: Freigaben und Abwesenheiten ---------- */
|
|
.dash-rand-zeile {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 10px 14px;
|
|
border-bottom: 1px solid var(--line-soft);
|
|
}
|
|
.dash-rand-zeile:last-child { border-bottom: none; }
|
|
.dash-avatar {
|
|
flex: 0 0 auto;
|
|
width: 28px;
|
|
height: 28px;
|
|
border-radius: 50%;
|
|
display: grid;
|
|
place-items: center;
|
|
font-size: 10.5px;
|
|
font-weight: 600;
|
|
background: var(--surface-sunk);
|
|
color: var(--text-muted);
|
|
}
|
|
.dash-rand-text { flex: 1; min-width: 0; }
|
|
.dash-rand-name {
|
|
display: block;
|
|
font-size: 12.5px;
|
|
font-weight: 500;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.dash-rand-meta {
|
|
display: block;
|
|
font-size: 10.5px;
|
|
color: var(--text-subtle);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.dash-chip {
|
|
flex: 0 0 auto;
|
|
font-size: 10px;
|
|
padding: 1px 7px;
|
|
border-radius: var(--r-pill);
|
|
background: var(--surface-sunk);
|
|
color: var(--text-muted);
|
|
white-space: nowrap;
|
|
}
|
|
/* Wer heute fehlt, ist die eigentliche Nachricht. */
|
|
.dash-chip.ist-heute { background: var(--red); color: #fff; }
|
|
|
|
@media (max-width: 1080px) {
|
|
.dash-zonen { grid-template-columns: 1fr; }
|
|
}
|
|
@media (max-width: 720px) {
|
|
.dash-auf-fortschritt { display: none; }
|
|
.dash-gruss { font-size: 19px; }
|
|
}
|
|
|
|
/* Farbe je Abwesenheitsart. Genutzt von .absence-chip (Projektbeteiligte,
|
|
Benutzerverwaltung, Projektbereich) und von .dash-chip im Dashboard.
|
|
.absence-chip-live ist entfallen — das Dashboard kennzeichnet eine heute
|
|
laufende Abwesenheit über .dash-chip.ist-heute. */
|
|
.absence-type-urlaub { background: rgba(36,138,61,.12); color: var(--green); border-color: transparent; }
|
|
.absence-type-krank { background: rgba(215,0,21,.10); color: var(--red); border-color: transparent; }
|
|
.absence-type-fortbildung { background: rgba(178,80,0,.12); color: var(--amber); border-color: transparent; }
|
|
|
|
/* Dark mode GANTT */
|
|
@media (prefers-color-scheme: dark) {
|
|
html:not([data-theme="light"]) .gantt-container { border-color: var(--line); }
|
|
html:not([data-theme="light"]) .gantt-row { border-color: var(--line); }
|
|
html:not([data-theme="light"]) .gantt-row.gantt-header,
|
|
html:not([data-theme="light"]) .gantt-row.gantt-days { border-color: var(--line); }
|
|
html:not([data-theme="light"]) .gantt-month { background: var(--surface-sunk); }
|
|
html:not([data-theme="light"]) .gantt-day.weekend { background: var(--surface-sunk); }
|
|
html:not([data-theme="light"]) .gantt-row.gantt-project.highlighted { background: var(--accent-soft); }
|
|
html:not([data-theme="light"]) .person-absences { background: var(--surface-sunk); border-color: var(--line); }
|
|
html:not([data-theme="light"]) .absence-chip { background: var(--surface); border-color: var(--line); }
|
|
html:not([data-theme="light"]) .absence-type-urlaub { background: rgba(48,209,88,.16); }
|
|
html:not([data-theme="light"]) .absence-type-krank { background: rgba(255,69,58,.16); }
|
|
html:not([data-theme="light"]) .absence-type-fortbildung { background: rgba(255,159,10,.16); }
|
|
}
|
|
|
|
html[data-theme="dark"] .gantt-container { border-color: var(--line); }
|
|
html[data-theme="dark"] .gantt-row { border-color: var(--line); }
|
|
html[data-theme="dark"] .gantt-row.gantt-header,
|
|
html[data-theme="dark"] .gantt-row.gantt-days { border-color: var(--line); }
|
|
html[data-theme="dark"] .gantt-month { background: var(--surface-sunk); }
|
|
html[data-theme="dark"] .gantt-day.weekend { background: var(--surface-sunk); }
|
|
html[data-theme="dark"] .gantt-row.gantt-project.highlighted { background: var(--accent-soft); }
|
|
html[data-theme="dark"] .person-absences { background: var(--surface-sunk); border-color: var(--line); }
|
|
html[data-theme="dark"] .absence-chip { background: var(--surface); border-color: var(--line); }
|
|
html[data-theme="dark"] .absence-type-urlaub { background: rgba(48,209,88,.16); }
|
|
html[data-theme="dark"] .absence-type-krank { background: rgba(255,69,58,.16); }
|
|
html[data-theme="dark"] .absence-type-fortbildung { background: rgba(255,159,10,.16); }
|
|
/* === Bereich „Einstellungen" (js/settings/settings.js) ===
|
|
Karten in einem Raster: „Mein Konto" und „Honorar" sind schmal, die
|
|
Vorlagen-Pflege bekommt über .settings-card-wide die volle Breite, weil
|
|
dort eine Liste mit Bezeichnung, Versatz/Dauer und Lösch-Knopf steht. Die
|
|
Felder der beiden firmenweiten Abschnitte nutzen weiterhin die Klassen aus
|
|
plan-styles.css (.honorar-anteil-field, .template-admin-row …) — sie sind
|
|
mit den Masken aus den Projekt-Dialogen hierher gezogen und mussten dafür
|
|
nicht umbenannt werden. */
|
|
.settings-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
|
gap: 16px;
|
|
align-items: start;
|
|
/* Seit immer nur EIN Abschnitt sichtbar ist (showAdminSection()), füllt
|
|
eine einzelne Karte sonst die ganze Fensterbreite — Beschriftung links,
|
|
Eingabefeld ganz rechts. Die Grenze hält Feld und Bezeichnung beisammen
|
|
und lässt der Aufwands-Matrix trotzdem genug Platz. */
|
|
max-width: 1180px;
|
|
}
|
|
/* Ausnahme fuer breite Abschnitte (Kennzeichen wide in ADMIN_SECTIONS,
|
|
js/admin/admin.js): Die Benutzertabelle hat zehn Spalten und scrollte in
|
|
der begrenzten Breite quer. Sie darf das Fenster ausnutzen. */
|
|
.settings-grid.is-wide { max-width: none; }
|
|
.settings-card-wide { grid-column: 1 / -1; }
|
|
|
|
.settings-card {
|
|
background: var(--surface);
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-lg);
|
|
padding: 16px 18px;
|
|
box-shadow: var(--sh-1);
|
|
}
|
|
.settings-card-title {
|
|
margin: 0 0 10px;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
}
|
|
/* Reichweite einer Einstellung — „firmenweit" heißt: wirkt bei allen. */
|
|
.settings-scope {
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
letter-spacing: .04em;
|
|
text-transform: uppercase;
|
|
color: var(--text-muted);
|
|
background: var(--surface-alt);
|
|
border-radius: var(--r-pill);
|
|
padding: 2px 8px;
|
|
}
|
|
|
|
.settings-kv { display: flex; flex-direction: column; gap: 2px; margin-bottom: 10px; }
|
|
.settings-kv-row {
|
|
display: flex;
|
|
gap: 10px;
|
|
font-size: 12.5px;
|
|
padding: 5px 0;
|
|
border-bottom: 1px solid var(--line-soft);
|
|
}
|
|
.settings-kv-row:last-child { border-bottom: none; }
|
|
.settings-kv-key { width: 150px; flex-shrink: 0; color: var(--text-muted); }
|
|
.settings-warn { color: var(--amber); }
|
|
.settings-email-edit { display: flex; gap: 8px; align-items: center; flex: 1; flex-wrap: wrap; }
|
|
.settings-email-edit input[type=email] { flex: 1; max-width: 320px; min-width: 160px; }
|
|
|
|
.settings-inline-field {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 12.5px;
|
|
margin-bottom: 10px;
|
|
}
|
|
.settings-inline-field select { min-width: 120px; }
|
|
|
|
/* KI-Zugangsdaten (Einstellungen -> Mein Konto, js/settings/settings.js):
|
|
ein umrandeter Block je Anbieter statt eines durchgehenden Feld-Rasters —
|
|
ChatGPT und Claude sind zwei getrennte Zugänge, keine vier gleichrangigen
|
|
Felder. Design-Entwurf mit Erik abgestimmt (Artifact d7ab2945). */
|
|
.ai-provider { border: 1px solid var(--line-soft); border-radius: var(--r-md); padding: 14px 16px; }
|
|
.ai-provider + .ai-provider { margin-top: 12px; }
|
|
.ai-provider-head { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-bottom: 12px; }
|
|
.ai-provider-info { min-width: 0; }
|
|
.ai-provider-mark {
|
|
width: 26px; height: 26px; border-radius: 7px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-size: 12px; font-weight: 700; color: #fff; flex-shrink: 0; letter-spacing: -.02em;
|
|
}
|
|
.ai-provider-mark.chatgpt { background: #10a37f; }
|
|
.ai-provider-mark.claude { background: #c96442; }
|
|
.ai-provider-name { font-weight: 600; font-size: 13px; }
|
|
.ai-provider-sub { font-size: 11.5px; color: var(--text-subtle); }
|
|
|
|
/* Status-Pill: derselbe Punkt-plus-Text-Aufbau wie an anderen Stellen der
|
|
App (z. B. .protokoll-status-dot), hier als Pill neben dem Anbieternamen. */
|
|
.ai-status-chip {
|
|
margin-left: auto;
|
|
display: inline-flex; align-items: center; gap: 6px;
|
|
font-size: 11.5px; font-weight: 500;
|
|
padding: 3px 10px 3px 8px;
|
|
border-radius: var(--r-pill);
|
|
background: var(--surface-alt);
|
|
color: var(--text-muted);
|
|
flex-shrink: 0;
|
|
}
|
|
.ai-status-chip .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--text-subtle); flex-shrink: 0; }
|
|
.ai-status-chip.is-on { color: var(--green); }
|
|
.ai-status-chip.is-on .dot { background: var(--green); }
|
|
|
|
.ai-provider-row { display: flex; gap: 8px; }
|
|
.ai-provider-row input[type=password],
|
|
.ai-provider-row input[type=text] {
|
|
flex: 1; min-width: 0;
|
|
padding: 7px 10px;
|
|
font-size: 12.5px;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--r-sm);
|
|
background: var(--surface);
|
|
color: var(--text);
|
|
font-family: inherit;
|
|
}
|
|
.ai-provider-row input::placeholder { color: var(--text-subtle); }
|
|
.ai-provider-row input:focus {
|
|
outline: none;
|
|
border-color: var(--accent);
|
|
box-shadow: 0 0 0 3px var(--accent-soft);
|
|
}
|
|
|
|
/* Häkchen-Knopf statt Textknopf "Speichern" — spricht für sich, direkt neben
|
|
dem Feld, das er betrifft. "Entfernen" bleibt ein leiser Text-Link, weil
|
|
er der seltenere, destruktivere Pfad ist. */
|
|
.ai-icon-btn {
|
|
flex-shrink: 0;
|
|
width: 30px; height: 30px; padding: 0;
|
|
border-radius: var(--r-sm);
|
|
border: 1px solid var(--accent);
|
|
background: var(--accent);
|
|
color: #fff;
|
|
display: flex; align-items: center; justify-content: center;
|
|
cursor: pointer;
|
|
transition: background .15s var(--ease), border-color .15s var(--ease);
|
|
}
|
|
.ai-icon-btn:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
|
|
/* flex-shrink:0 ist hier kein Stil-Detail, sondern die Abhilfe für einen
|
|
Chromium-Eigenheit: ein <svg> als Flex-Kind schrumpft ohne explizites
|
|
flex-shrink:0 auf Breite 0 (min-width:auto berechnet die Inhaltsgröße
|
|
eines SVG offenbar als 0), wodurch das Häkchen-Icon unsichtbar wurde. */
|
|
.ai-icon-btn svg { width: 15px; height: 15px; flex-shrink: 0; }
|
|
|
|
.ai-text-btn {
|
|
flex-shrink: 0;
|
|
border: none; background: none;
|
|
font-size: 11.5px; color: var(--text-subtle);
|
|
cursor: pointer; padding: 0 2px;
|
|
}
|
|
.ai-text-btn:hover { color: var(--red); }
|
|
.ai-text-btn:disabled { opacity: .4; cursor: default; }
|
|
.ai-text-btn:disabled:hover { color: var(--text-subtle); }
|
|
|
|
/* Cloud/Eigener-Server-Umschalter: ersetzt den früheren, eingeklappten
|
|
"API-URL"-Link. Ein eigener Server (LM Studio, Ollama) ist ein Kernfall,
|
|
kein Nebendetail — genau das wollte Erik als Erstes ausprobieren, als
|
|
er die Karte in echt benutzte — deshalb jetzt eine sichtbare Wahl statt
|
|
eines kleinen Links, den man erst entdecken muss. */
|
|
.ai-mode-toggle {
|
|
display: inline-flex; background: var(--surface-sunk); border-radius: var(--r-pill);
|
|
padding: 3px; gap: 2px; margin-top: 12px;
|
|
}
|
|
.ai-mode-toggle button {
|
|
border: none; background: none; padding: 5px 13px; border-radius: var(--r-pill);
|
|
font-size: 12px; font-weight: 500; color: var(--text-muted); cursor: pointer; font-family: inherit;
|
|
}
|
|
.ai-mode-toggle button.is-active { background: var(--surface); color: var(--text); font-weight: 600; box-shadow: var(--sh-1); }
|
|
|
|
.ai-url-panel { margin-top: 12px; padding-top: 12px; border-top: 1px dashed var(--line-soft); display: none; }
|
|
.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
|
|
gespeichert ist. Umrandet statt gefüllt, da kein primärer Speichern-
|
|
Knopf — sitzt direkt neben dem Status-Pill, den er betrifft. */
|
|
.ai-reconnect-btn {
|
|
flex-shrink: 0; width: 26px; height: 26px; padding: 0; border-radius: 50%;
|
|
border: 1px solid var(--line); background: var(--surface); color: var(--text-muted);
|
|
display: flex; align-items: center; justify-content: center; cursor: pointer;
|
|
transition: background .15s var(--ease), color .15s var(--ease), border-color .15s var(--ease);
|
|
}
|
|
.ai-reconnect-btn:hover { background: var(--surface-alt); color: var(--accent); border-color: var(--accent); }
|
|
.ai-reconnect-btn:disabled { opacity: .4; cursor: default; }
|
|
.ai-reconnect-btn:disabled:hover { background: var(--surface); color: var(--text-muted); border-color: var(--line); }
|
|
.ai-reconnect-btn svg { width: 13px; height: 13px; flex-shrink: 0; }
|
|
.ai-reconnect-btn.is-testing svg { animation: ai-reconnect-spin .9s linear infinite; }
|
|
@keyframes ai-reconnect-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
|
|
|
/* Fehler aus "Neu verbinden": bleibt stehen, bis der nächste Test läuft
|
|
oder erfolgreich ist — kein flüchtiger Hinweis. Dieselbe Lehre wie beim
|
|
Protokoll-Import-Banner: ein Toast neben einer sich gleichzeitig
|
|
ändernden Ansicht wird leicht übersehen. :empty blendet die Zeile aus,
|
|
solange kein Fehler ansteht, ohne eigenes hidden-Attribut in JS. */
|
|
.ai-test-error { font-size: 11.5px; color: var(--red); margin-top: 8px; }
|
|
.ai-test-error:empty { display: none; }
|
|
|
|
/* Bestätigung nach dem Speichern. Bewusst kein alert(): ein gelungener
|
|
Speichervorgang soll den Arbeitsfluss nicht anhalten. */
|
|
.settings-note {
|
|
margin-top: 14px;
|
|
font-size: 12.5px;
|
|
color: var(--green);
|
|
opacity: 0;
|
|
transition: opacity .25s var(--ease);
|
|
}
|
|
.settings-note.show { opacity: 1; }
|
|
|
|
/* Felder und Matrix der firmenweiten Einstellungen (js/settings/settings.js).
|
|
Die Aufwands-Matrix kann breiter werden als die Karte — sie scrollt dann in
|
|
ihrem eigenen Container, statt die Seite quer zu ziehen. */
|
|
.settings-field-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
|
|
gap: 8px 16px;
|
|
margin-bottom: 12px;
|
|
}
|
|
.settings-field {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 10px;
|
|
font-size: 12.5px;
|
|
}
|
|
.settings-field input { width: 88px; padding: 4px 7px; font-size: 12.5px; text-align: right; }
|
|
|
|
.settings-check {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 12.5px;
|
|
margin: 4px 0 2px;
|
|
}
|
|
|
|
.settings-subtitle {
|
|
margin: 16px 0 4px;
|
|
font-size: 12.5px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.settings-matrix-wrap { overflow-x: auto; margin-bottom: 10px; }
|
|
.settings-matrix { border-collapse: collapse; font-size: 12px; }
|
|
.settings-matrix th, .settings-matrix td {
|
|
padding: 3px 6px;
|
|
border-bottom: 1px solid var(--line-soft);
|
|
text-align: center;
|
|
}
|
|
.settings-matrix thead th { color: var(--text-muted); font-weight: 500; white-space: nowrap; }
|
|
.settings-matrix tbody th { text-align: left; font-weight: 500; white-space: nowrap; }
|
|
.settings-matrix input { width: 62px; padding: 3px 6px; font-size: 12px; text-align: right; }
|
|
|
|
.settings-holiday-list { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; }
|
|
|
|
/* Abweichende Stelle im Auslastungs-Tab (js/load/load.js) — steht nur da,
|
|
wenn die Person nicht voll beschäftigt ist. */
|
|
.load-capacity {
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
color: var(--text-muted);
|
|
background: var(--surface-alt);
|
|
border-radius: var(--r-pill);
|
|
padding: 1px 7px;
|
|
margin-left: 6px;
|
|
}
|
|
|
|
/* Benutzerverwaltung (Abschnitt „Benutzerverwaltung" des Bereichs #admin,
|
|
js/admin-users.js). Diese Regeln standen als <style>-Block in der frueheren
|
|
eigenstaendigen Seite public/admin-users.html; die Regeln der dortigen
|
|
Seiten-Huelle (.admin-page/.admin-header/.admin-body) sind mit ihr
|
|
entfallen, weil der Abschnitt jetzt in der normalen App-Huelle liegt. */
|
|
.admin-toolbar { margin-bottom: 14px; }
|
|
td.actions-cell { white-space: nowrap; }
|
|
td.actions-cell button { margin: 2px; }
|
|
.badge-active { color: var(--green); font-weight: 600; }
|
|
.badge-inactive { color: var(--text-muted); }
|
|
.badge-warn { color: var(--amber); font-weight: 600; }
|
|
/* Das einmalig angezeigte Passwort laesst sich mit einem Klick markieren. */
|
|
#generatedPasswordBox {
|
|
display: none;
|
|
margin-bottom: 14px;
|
|
padding: 12px 14px;
|
|
border-radius: var(--r-md);
|
|
background: var(--surface-alt);
|
|
border: 1px solid var(--line-soft);
|
|
font-size: 13px;
|
|
}
|
|
#generatedPasswordBox b { user-select: all; }
|
|
|
|
/* === Verlauf: die Kacheln der Vorwochen (js/wochenbericht/wochenbericht.js)
|
|
Im wöchentlichen Teammeeting wird jedes Projekt angesehen; der Verlauf
|
|
zeigt auf Knopfdruck, wie der Stand in den Sitzungen davor war. Waagerecht
|
|
und scrollbar, weil der Bericht ohnehin sehr lang ist — eine Liste
|
|
untereinander hätte ihn je Projekt um ein Vielfaches verlängert. === */
|
|
.timeline-block {
|
|
grid-column: 1 / -1;
|
|
margin-top: 12px;
|
|
border-top: 1px solid var(--line-soft);
|
|
padding-top: 10px;
|
|
}
|
|
.timeline-block > summary {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
cursor: pointer;
|
|
font-size: 12.5px;
|
|
color: var(--text-muted);
|
|
list-style: none;
|
|
user-select: none;
|
|
}
|
|
.timeline-block > summary::-webkit-details-marker { display: none; }
|
|
/* Der Zähler schiebt sich sonst über die ganze Kartenbreite ans rechte Ende
|
|
(margin-left:auto von .comment-history-count) und steht dort verloren —
|
|
hier gehört er direkt an das Wort "Verlauf". */
|
|
.timeline-block > summary .comment-history-count { margin-left: 0; }
|
|
.timeline-block > summary:hover { color: var(--text); }
|
|
.timeline-block[open] > summary .comment-history-caret { transform: rotate(90deg); }
|
|
|
|
.timeline-body { padding-top: 10px; }
|
|
|
|
/* Die Schiene scrollt waagerecht; die Karte selbst darf dabei nicht breiter
|
|
werden, deshalb min-width:0 am Elternraster (siehe .board-drawer). */
|
|
.timeline-rail {
|
|
display: flex;
|
|
gap: 10px;
|
|
overflow-x: auto;
|
|
padding-bottom: 6px;
|
|
scroll-snap-type: x proximity;
|
|
}
|
|
.timeline-card {
|
|
flex: 0 0 190px;
|
|
scroll-snap-align: start;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
padding: 10px 11px;
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-md);
|
|
background: var(--surface-alt);
|
|
font-size: 12px;
|
|
}
|
|
.timeline-card-head {
|
|
display: flex;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
gap: 6px;
|
|
}
|
|
.timeline-kw { font-weight: 600; white-space: nowrap; }
|
|
.timeline-datum { color: var(--text-muted); font-size: 11px; white-space: nowrap; }
|
|
|
|
/* Der Kommentar wird auf sechs Zeilen begrenzt, damit alle Kacheln einer
|
|
Schiene gleich hoch bleiben; der vollständige Text steht im Tooltip. */
|
|
.timeline-text {
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 6;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
line-height: 1.45;
|
|
min-height: 3em;
|
|
}
|
|
.timeline-leer { color: var(--text-muted); font-style: italic; }
|
|
|
|
.timeline-lph-list { display: flex; flex-direction: column; gap: 1px; }
|
|
.timeline-lph {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
font-variant-numeric: tabular-nums;
|
|
color: var(--text-muted);
|
|
}
|
|
.timeline-termine { display: flex; flex-direction: column; gap: 1px; }
|
|
.timeline-termin { color: var(--text-muted); font-size: 11.5px; }
|
|
|
|
/* Gegenüber der Kachel DAVOR geänderte Werte. Dezent, aber auffindbar: Bei
|
|
zehn Leistungsphasen findet man die eine geänderte Zahl sonst nicht. */
|
|
.timeline-lph.is-changed,
|
|
.timeline-termin.is-changed { color: var(--accent); font-weight: 600; }
|
|
|
|
/* "übernehmen" in der Verlaufs-Kachel: übernimmt die einzige Funktion, die
|
|
der entfallene Aufklapper "Frühere Sitzungen" über das Anzeigen hinaus
|
|
hatte. Dezent, weil es in jeder Kachel steht und nicht mit dem Kommentar
|
|
um Aufmerksamkeit ringen soll. */
|
|
.timeline-take {
|
|
align-self: flex-start;
|
|
padding: 1px 8px;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--r-pill);
|
|
background: transparent;
|
|
color: var(--text-muted);
|
|
font-family: inherit;
|
|
font-size: 10.5px;
|
|
cursor: pointer;
|
|
}
|
|
.timeline-take:hover { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
.timeline-take:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
|
|
/* Der Verfasser steht in der dichten Fassung in der Fußzeile neben den
|
|
Knöpfen statt in einer eigenen Kopfzeile darüber. */
|
|
/* Der Verfasser teilt sich die Fußzeile mit den Knöpfen. Er darf sie nicht
|
|
zweizeilig machen — deshalb bleibt er auf einer Zeile und wird lieber
|
|
gekürzt; der vollständige Eintrag steht ohnehin im Verlauf. */
|
|
.comment-foot-author {
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
font-size: 11px;
|
|
color: var(--text-subtle);
|
|
}
|
|
.comment-foot-author * { display: inline; white-space: nowrap; }
|
|
/* Das Verfasser-Kürzel klebte sonst am Namen: Der Avatar ist in der Fußzeile
|
|
inline gesetzt und bringt keinen eigenen Abstand mehr mit. */
|
|
.comment-foot-author .comment-av { margin-right: 4px; }
|
|
.comment-foot-author .comment-sep { margin: 0 4px; }
|
|
|
|
.timeline-people { display: flex; flex-wrap: wrap; gap: 3px; margin-top: auto; padding-top: 2px; }
|
|
.timeline-chip {
|
|
font-size: 10.5px;
|
|
font-weight: 600;
|
|
padding: 1px 6px;
|
|
border-radius: var(--r-pill);
|
|
background: var(--surface-sunk);
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
/* === Wochenbericht als Tagesordnung, Fokus-Modus "Aktenregal" (Eriks Wahl
|
|
aus dem Entwurfsvergleich, js/wochenbericht/wochenbericht.js) ===
|
|
Links eine Leiste mit Nummer, Gruppe UND Name direkt sichtbar (keine
|
|
schmale Icon-Spalte mit Namen nur im Hover-Tooltip mehr), rechts das
|
|
gewählte Projekt: Kommentar (inkl. Scrollrad zum Verlauf), darunter
|
|
Leistungsphasen und Beteiligte dauerhaft sichtbar nebeneinander
|
|
(.bericht-meta-grid). Nur die Anwesenheit der Sitzung bleibt ein
|
|
aufklappbarer Abschnitt (.bericht-disclosure) - sie ist sitzungs-, nicht
|
|
projektbezogen und sitzt am Seitenfuß, unabhängig vom gewählten Projekt.
|
|
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-panel {
|
|
background: var(--surface); border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-lg); box-shadow: var(--sh-1); overflow: hidden;
|
|
}
|
|
|
|
.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;
|
|
/* background-color statt background (Kurzform): die Kurzform setzt das vom
|
|
globalen select-Selektor gesetzte Pfeil-Icon (background-image/-repeat/
|
|
-position) auf die Vorgabewerte zurück, wodurch das 10x6px-Pfeilchen
|
|
über das ganze Feld kachelt statt einmal rechts zu sitzen - derselbe
|
|
Fehler wie zuvor bei .pc-select. */
|
|
border-radius: var(--r-sm); background-color: 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 {
|
|
display: flex; align-items: center; gap: 14px;
|
|
padding: 13px 16px; border-radius: var(--r-md);
|
|
background: var(--surface); border: 1px solid var(--line-soft);
|
|
margin-bottom: 8px; cursor: pointer; transition: background .15s var(--ease);
|
|
}
|
|
.planung-row:hover { background: var(--surface-alt); }
|
|
.planung-row-icon {
|
|
width: 34px; height: 34px; border-radius: 9px; flex: 0 0 auto;
|
|
display: flex; align-items: center; justify-content: center; font-size: 14px;
|
|
}
|
|
.planung-row-icon.honorar { background: var(--accent-soft); color: var(--accent); }
|
|
.planung-row-icon.leer { background: var(--surface-sunk); color: var(--text-subtle); }
|
|
.planung-row-main { flex: 1; min-width: 0; }
|
|
.planung-row-name { font-size: 14px; font-weight: 600; margin: 0; }
|
|
.planung-row-meta { font-size: 12px; color: var(--text-muted); margin: 2px 0 0; }
|
|
.planung-row-stat { text-align: right; font-size: 12px; color: var(--text-muted); white-space: nowrap; }
|
|
.planung-row-stat b { display: block; color: var(--text); font-size: 13px; font-variant-numeric: tabular-nums; }
|
|
|
|
.planung-choice-cards { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
|
.choice-card {
|
|
border: 1.5px solid var(--line); border-radius: var(--r-md); padding: 14px 14px 12px;
|
|
cursor: pointer; background: var(--surface-alt); transition: border-color .15s ease;
|
|
}
|
|
.choice-card:hover { border-color: var(--accent); }
|
|
.choice-card.is-gewaehlt { border-color: var(--accent); }
|
|
.choice-card-icon { font-size: 18px; margin-bottom: 8px; display: block; }
|
|
.choice-card-title { font-size: 13px; font-weight: 600; margin-bottom: 3px; }
|
|
.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; }
|
|
|
|
/* ---------- 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);
|
|
}
|
|
.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; }
|
|
|
|
.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); }
|
|
/* Entwurf "Aktenregal" (zweite Fassung des Fokus-Modus, Eriks Wahl aus dem
|
|
Entwurfsvergleich): die Leiste zeigt Nummer, Gruppe UND Name direkt statt
|
|
nur eine Nummer mit Name im Hover-Tooltip (die vorherige, schmale 64px-
|
|
Fassung) — man sieht die ganze Tagesordnung auf einen Blick, nicht nur
|
|
beim Drüberfahren. */
|
|
.bericht-layout { display: grid; grid-template-columns: 240px minmax(0, 1fr); }
|
|
@media (max-width: 860px) { .bericht-layout { grid-template-columns: 1fr; } }
|
|
|
|
.bericht-rail {
|
|
background: var(--surface-alt);
|
|
border-right: 1px solid var(--line-soft);
|
|
padding: 10px 8px; display: flex; flex-direction: column; gap: 2px;
|
|
position: sticky; top: 64px; max-height: calc(100vh - 64px); overflow-y: auto;
|
|
}
|
|
.bericht-rail-gruppe {
|
|
font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
|
|
color: var(--text-subtle); padding: 12px 8px 5px;
|
|
}
|
|
.bericht-rail-gruppe:first-child { padding-top: 2px; }
|
|
.bericht-rail-item {
|
|
position: relative; display: flex; align-items: center; gap: 8px;
|
|
padding: 6px 8px; border-radius: var(--r-sm); cursor: pointer; color: var(--text); font-size: 12.5px;
|
|
}
|
|
.bericht-rail-item:hover { background: var(--surface-sunk); }
|
|
.bericht-rail-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
|
|
.bericht-rail-item.is-aktiv { background: var(--accent-soft); }
|
|
.bericht-rail-item.is-aktiv .bericht-rail-name { color: var(--accent); font-weight: 650; }
|
|
/* 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: 11px; color: var(--text-subtle); font-variant-numeric: tabular-nums; flex-shrink: 0; width: 16px; }
|
|
.bericht-rail-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
|
|
.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-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
/* 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-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-change-pill {
|
|
font-size: 10.5px; font-weight: 700; color: #fff; padding: 2px 9px; border-radius: var(--r-pill);
|
|
}
|
|
.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 {
|
|
display: inline-flex; align-items: center; gap: 6px;
|
|
color: var(--amber); font-size: 12px; margin-bottom: 16px;
|
|
}
|
|
|
|
/* Scrollrad über dem Kommentar-Eingabefeld: "Was hatten wir letzte Woche
|
|
vereinbart?" ist die häufigste Frage im Meeting - stand bis hierhin als
|
|
starre Einzeile mit nur der unmittelbaren Vorwoche da. Erik wollte mit dem
|
|
Mausrad durch MEHRERE vergangene Kommentare blättern können
|
|
(renderVorwocheRad(), js/wochenbericht/wochenbericht.js), nicht nur die
|
|
eine Vorwoche sehen - die Quelle ist derselbe, bereits geprüfte Verlauf wie
|
|
im Verlaufs-Dialog (projectStatusHistory(), js/utils.js). */
|
|
.bericht-vorwoche-rad {
|
|
display: flex; align-items: center; gap: 8px; background: var(--surface-sunk);
|
|
border-radius: var(--r-md); padding: 7px 9px; margin-bottom: 14px; cursor: ns-resize;
|
|
}
|
|
.bericht-vorwoche-nav {
|
|
width: 22px; height: 22px; border: none; background: none; border-radius: 50%;
|
|
color: var(--text-muted); cursor: pointer; font-size: 13px; flex-shrink: 0;
|
|
display: flex; align-items: center; justify-content: center; font-family: inherit;
|
|
}
|
|
.bericht-vorwoche-nav:hover:not(:disabled) { background: var(--surface); color: var(--text); }
|
|
.bericht-vorwoche-nav:disabled { opacity: .3; cursor: default; }
|
|
.bericht-vorwoche-inhalt { flex: 1; min-width: 0; }
|
|
.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-text {
|
|
color: var(--text-muted); font-size: 12px; display: block;
|
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
}
|
|
.bericht-vorwoche-dots { display: flex; flex-direction: column; gap: 3px; flex-shrink: 0; }
|
|
.bericht-vorwoche-dots .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--line); }
|
|
.bericht-vorwoche-dots .dot.aktiv { background: var(--accent); }
|
|
|
|
.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; }
|
|
.protokoll-text {
|
|
width: 100%; font-family: ui-monospace, 'SF Mono', monospace;
|
|
font-size: 12px; line-height: 1.5; resize: vertical;
|
|
}
|
|
|
|
/* === Projektbereich: Leistungsstand & Abrechnung ===========================
|
|
Karte im Dashboard des Projektbereichs (renderPlanLeistung() in
|
|
js/plan/plan-leistung.js): erbrachter Leistungswert aus Honorar, HOAI-Anteil
|
|
je Leistungsphase und Fortschritt der Phase. Ausschliesslich Design-Tokens,
|
|
damit die Karte in hellem und dunklem Thema ohne eigene Dark-Regeln traegt.
|
|
Die Tabelle nutzt .lp-matrix als Grundlage; .num/.strong sind hier eigene
|
|
Regeln, weil die gleichnamigen des Honorar-Dialogs auf .honorar-table
|
|
eingeschraenkt sind (plan-styles.css). */
|
|
|
|
.leistung-kpis {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
margin-bottom: 12px;
|
|
}
|
|
.leistung-kpi {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
flex: 1 1 160px;
|
|
min-width: 150px;
|
|
padding: 10px 12px;
|
|
background: var(--surface-alt);
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-md);
|
|
}
|
|
.leistung-kpi-label {
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
color: var(--text-muted);
|
|
text-transform: uppercase;
|
|
letter-spacing: .03em;
|
|
}
|
|
.leistung-kpi-value {
|
|
font-size: 19px;
|
|
font-weight: 600;
|
|
font-variant-numeric: tabular-nums;
|
|
line-height: 1.2;
|
|
}
|
|
.leistung-kpi-value.accent { color: var(--accent); }
|
|
.leistung-kpi-sub {
|
|
font-size: 11.5px;
|
|
color: var(--text-subtle);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
/* Fortschrittsbalken: derselbe Prozentwert wie die Kennzahl darueber, nur als
|
|
Flaeche — bei zehn Leistungsphasen ist die Gesamtlage sonst nur aus der
|
|
Tabelle zu erschliessen. */
|
|
.leistung-balken {
|
|
height: 7px;
|
|
border-radius: var(--r-pill);
|
|
background: var(--surface-sunk);
|
|
overflow: hidden;
|
|
margin: 0 0 14px;
|
|
}
|
|
.leistung-balken > span {
|
|
display: block;
|
|
height: 100%;
|
|
background: var(--accent);
|
|
border-radius: var(--r-pill);
|
|
transition: width .25s var(--ease);
|
|
}
|
|
|
|
.leistung-table { font-size: 12.5px; }
|
|
.leistung-table th.num,
|
|
.leistung-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
|
|
.leistung-table td.strong { font-weight: 600; }
|
|
.leistung-table tfoot th,
|
|
.leistung-table tfoot td {
|
|
border-top: 1px solid var(--line);
|
|
padding: 6px 8px;
|
|
font-weight: 600;
|
|
background: var(--surface-alt);
|
|
}
|
|
|
|
/* Hinweis, wenn die Rechnung nicht moeglich ist (kein Honorar, keine
|
|
beauftragte Phase, keine Anteile). Bewusst als Text und nicht als "0 EUR":
|
|
unbekannt ist nicht dasselbe wie nichts erbracht. */
|
|
.leistung-leer {
|
|
font-size: 12.5px;
|
|
color: var(--text-muted);
|
|
padding: 10px 12px;
|
|
background: var(--surface-alt);
|
|
border-left: 2px solid var(--amber);
|
|
border-radius: 0 var(--r-sm) var(--r-sm) 0;
|
|
}
|
|
|
|
/* === Auftragsstatus: Kennzeichen an der Projektzeile und Auswahlfeld in den
|
|
Pflegemasken (js/auftrag.js) ===
|
|
Die Farbflächen entstehen mit color-mix aus den Ampel-Tokens, damit sie im
|
|
hellen und im dunklen Erscheinungsbild von selbst mitwandern (feste rgba-
|
|
Flächen tragen im Dark Mode nicht, siehe --attend-bg). Die Schriftfarbe ist
|
|
dasselbe Token — es dreht je Erscheinungsbild ins jeweils kontrastreichere
|
|
Ende. */
|
|
.auftrag-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
border-radius: var(--r-pill);
|
|
padding: 1px 8px;
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
white-space: nowrap;
|
|
background: var(--surface-sunk);
|
|
border: 1px solid var(--line-soft);
|
|
color: var(--text-muted);
|
|
}
|
|
/* Anfrage: hereinkommend, noch nichts entschieden. */
|
|
.auftrag-chip-anfrage {
|
|
background: color-mix(in srgb, var(--accent) 12%, transparent);
|
|
border-color: color-mix(in srgb, var(--accent) 30%, transparent);
|
|
color: var(--accent);
|
|
}
|
|
/* Angebot: draußen, Entscheidung offen — dieselbe Warnfarbe wie die Ampel. */
|
|
.auftrag-chip-angebot {
|
|
background: color-mix(in srgb, var(--amber) 12%, transparent);
|
|
border-color: color-mix(in srgb, var(--amber) 30%, transparent);
|
|
color: var(--amber);
|
|
}
|
|
/* Beauftragt: der Regelfall. Wird an der Projektzeile normalerweise gar nicht
|
|
gezeigt (auftragsstatusChip() gibt dafür einen leeren String zurück), nur in
|
|
Masken, die den Status ausdrücklich ausweisen. */
|
|
.auftrag-chip-beauftragt {
|
|
background: color-mix(in srgb, var(--green) 12%, transparent);
|
|
border-color: color-mix(in srgb, var(--green) 30%, transparent);
|
|
color: var(--green);
|
|
}
|
|
/* Abgeschlossen: erledigt, tritt bewusst zurück. */
|
|
.auftrag-chip-abgeschlossen {
|
|
background: var(--surface-sunk);
|
|
border-color: var(--line);
|
|
color: var(--text-subtle);
|
|
}
|
|
|
|
/* Status-Chip einer Sitzung in der Admin-Liste "Termine & Sitzungen"
|
|
(renderAdminSitzungen(), js/admin/admin.js) — dasselbe Muster wie
|
|
.auftrag-chip: eine neutrale Grundform, drei eingefärbte Zustände. */
|
|
.sitzung-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
border-radius: var(--r-pill);
|
|
padding: 1px 8px;
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
white-space: nowrap;
|
|
background: var(--surface-sunk);
|
|
border: 1px solid var(--line-soft);
|
|
color: var(--text-muted);
|
|
}
|
|
.sitzung-chip.ist-geoeffnet {
|
|
background: color-mix(in srgb, var(--green) 12%, transparent);
|
|
border-color: color-mix(in srgb, var(--green) 30%, transparent);
|
|
color: var(--green);
|
|
}
|
|
.sitzung-chip.ist-entsperrt {
|
|
background: color-mix(in srgb, var(--accent) 12%, transparent);
|
|
border-color: color-mix(in srgb, var(--accent) 30%, transparent);
|
|
color: var(--accent);
|
|
}
|
|
.sitzung-chip.ist-gesperrt {
|
|
background: color-mix(in srgb, var(--amber) 12%, transparent);
|
|
border-color: color-mix(in srgb, var(--amber) 30%, transparent);
|
|
color: var(--amber);
|
|
}
|
|
|
|
.auftrag-select {
|
|
font-size: 12px;
|
|
padding: 3px 8px;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--r-sm);
|
|
background: var(--surface);
|
|
color: var(--text);
|
|
max-width: 100%;
|
|
}
|
|
.auftrag-select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
|
|
/* ===========================================================
|
|
Ein Ort je Angabe — Anzeige statt Eingabe
|
|
===========================================================
|
|
Kommentar, Fortschritt und Zuordnung werden nur noch im Wochenbericht
|
|
erfasst. Die Projektliste und der Projektbereich zeigen dieselben Werte in
|
|
derselben Anordnung, aber ohne Eingabefelder (Klasse is-ansicht) — daneben
|
|
steht der Weg zur Bearbeitung (.drawer-verweis/.plan-verweis). */
|
|
|
|
/* Der Kommentar als Text: dieselbe Fläche wie der Editor, nur ruhiger. */
|
|
.status-edit.is-ansicht{
|
|
cursor: default;
|
|
background: var(--surface-sunk);
|
|
border-style: dashed;
|
|
}
|
|
|
|
.board-lph-cell.is-ansicht{ cursor: default; }
|
|
|
|
/* Ein Kürzel, das nicht angeklickt werden kann, zeigt das auch. */
|
|
.board-avatar.is-ansicht{ cursor: default; }
|
|
|
|
/* Der Verweis auf den Erfassungsort. Er steht unter der Karte, in einer
|
|
Zeile mit der Begründung, warum hier nichts einzugeben ist. */
|
|
.drawer-verweis{
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 8px 2px 2px;
|
|
}
|
|
.plan-verweis{
|
|
background: none;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--r-pill);
|
|
padding: 3px 12px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
color: var(--accent);
|
|
cursor: pointer;
|
|
}
|
|
.plan-verweis:hover{ background: var(--accent-soft); }
|
|
.plan-verweis:focus-visible{ outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
|
|
/* ===========================================================
|
|
Projektliste: die Listenoperationen des früheren Projekte-Tabs
|
|
=========================================================== */
|
|
|
|
/* Öffnen/Löschen am Zeilenende. Sichtbar erst beim Überfahren der Zeile,
|
|
damit die Liste im Ruhezustand ruhig bleibt — auf Tastatur-Fokus aber
|
|
ebenfalls, sonst wären die Knöpfe nicht erreichbar. */
|
|
.board-row-actions{
|
|
display: flex;
|
|
gap: 6px;
|
|
flex: 0 0 auto;
|
|
opacity: 0;
|
|
transition: opacity .12s;
|
|
}
|
|
.board-row:hover .board-row-actions,
|
|
.board-row.open .board-row-actions,
|
|
.board-row-actions:focus-within{ opacity: 1; }
|
|
|
|
/* Der Ziehgriff steht vor dem Aufklapp-Pfeil. */
|
|
.board-row .drag-handle{
|
|
flex: 0 0 auto;
|
|
cursor: grab;
|
|
color: var(--text-subtle);
|
|
display: inline-flex;
|
|
align-items: center;
|
|
}
|
|
.board-row .drag-handle:active{ cursor: grabbing; }
|
|
|
|
/* Die Gruppe ist eine Ablagefläche: sie behält eine Mindesthöhe, damit eine
|
|
leer geräumte Gruppe ein Ziel bleibt. */
|
|
.board-list.projects-sortable{ min-height: 34px; }
|
|
|
|
/* Der Verweis unter einer Anzeige-Karte im Projektbereich. */
|
|
.plan-verweis-row{
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
padding-top: 10px;
|
|
}
|
|
.plan-verweis-row:empty{ display: none; }
|
|
|
|
/* Die Schlagwörter-Liste in den Projekteinstellungen darf umbrechen. */
|
|
#settingsProjTags{
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
#settingsProjTags .tag-input{ width: 110px; }
|
|
|
|
/* === Dashboard: Termine meiner Projekte ===
|
|
Die Aufgaben und Meilensteine der Projekte, denen man zugeordnet ist
|
|
(GET /my-dashboard, Feld projectDates). Bewusst eine Liste und keine
|
|
Kartenwand wie "Meine Aufgaben": Hier geht es um Reihenfolge in der Zeit,
|
|
und Termine, die untereinander stehen, lassen sich der Reihe nach
|
|
abarbeiten. */
|
|
/* Die Gruppen sitzen in derselben .dash-karte wie die eigenen Aufgaben
|
|
darüber — beides ist der eigene Arbeitsstrom und soll gleich aussehen.
|
|
Die Titelzeile ist deshalb ein abgesetzter Streifen innerhalb der Karte
|
|
und kein freistehender Abschnittstitel mehr. */
|
|
.fristen-gruppe + .fristen-gruppe { border-top: 1px solid var(--line); }
|
|
|
|
.fristen-gruppe-titel {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 9px;
|
|
padding: 9px 16px 6px;
|
|
background: var(--surface-alt);
|
|
font-size: 10.5px;
|
|
font-weight: 600;
|
|
letter-spacing: .06em;
|
|
text-transform: uppercase;
|
|
color: var(--text-muted);
|
|
}
|
|
.fristen-gruppe-titel span {
|
|
font-size: 10px;
|
|
padding: 1px 7px;
|
|
border-radius: var(--r-pill);
|
|
background: var(--surface-sunk);
|
|
color: var(--text-muted);
|
|
letter-spacing: 0;
|
|
}
|
|
.fristen-gruppe.ist-ueberfaellig .fristen-gruppe-titel { color: var(--red); }
|
|
.fristen-gruppe.ist-ueberfaellig .fristen-gruppe-titel span {
|
|
background: var(--red);
|
|
color: #fff;
|
|
}
|
|
.fristen-gruppe.ist-bald .fristen-gruppe-titel { color: var(--amber); }
|
|
|
|
/* Eine Zeile: Datum — Art — Bezeichnung — Projekt — Zuständige.
|
|
Die Bezeichnung nimmt den freien Platz, alles andere hat feste Breiten,
|
|
damit die Spalten über alle Zeilen fluchten. */
|
|
.fristen-zeile {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 14px;
|
|
padding: 9px 16px;
|
|
border-bottom: 1px solid var(--line-soft);
|
|
text-decoration: none;
|
|
color: inherit;
|
|
transition: background .12s var(--ease);
|
|
}
|
|
.fristen-zeile:last-child { border-bottom: none; }
|
|
.fristen-zeile:hover { background: var(--surface); }
|
|
.fristen-zeile:focus-visible {
|
|
outline: 2px solid var(--accent);
|
|
outline-offset: -2px;
|
|
}
|
|
|
|
.fristen-datum {
|
|
flex: 0 0 104px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
line-height: 1.25;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.fristen-datum b { font-size: 13px; font-weight: 600; }
|
|
.fristen-datum em {
|
|
font-style: normal;
|
|
font-size: 11px;
|
|
color: var(--text-subtle);
|
|
}
|
|
.ist-ueberfaellig .fristen-datum b,
|
|
.ist-ueberfaellig .fristen-datum em { color: var(--red); }
|
|
.ist-bald .fristen-datum b { color: var(--amber); }
|
|
|
|
.fristen-art {
|
|
flex: 0 0 auto;
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
padding: 2px 8px;
|
|
border-radius: var(--r-pill);
|
|
background: var(--surface-sunk);
|
|
color: var(--text-muted);
|
|
white-space: nowrap;
|
|
}
|
|
.fristen-art.ist-meilenstein {
|
|
background: var(--accent-soft);
|
|
color: var(--accent);
|
|
}
|
|
|
|
.fristen-titel {
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
font-size: 13.5px;
|
|
font-weight: 500;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.fristen-projekt {
|
|
flex: 0 0 auto;
|
|
max-width: 230px;
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.fristen-wer {
|
|
flex: 0 0 140px;
|
|
font-size: 11px;
|
|
color: var(--text-subtle);
|
|
text-align: right;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Die Warn-Kachel im Kennzahlenband. Eine Null bleibt neutral — "0
|
|
überfällig" ist eine gute Nachricht und darf nicht rot sein (siehe
|
|
renderStatTile()). */
|
|
.load-num-warnung { color: var(--red); }
|
|
|
|
@media (max-width: 860px) {
|
|
.fristen-projekt, .fristen-wer { display: none; }
|
|
.fristen-datum { flex-basis: 92px; }
|
|
}
|
|
|
|
/* Überfälliges Datum in der Aufgabenkarte des Dashboards — dieselbe
|
|
Kennzeichnung wie in der Terminliste darunter, damit die Kachel
|
|
"Überfällig" und die Listen zusammenpassen. */
|
|
.load-detail b.ist-ueberfaellig { color: var(--red); }
|
|
|
|
/* === Assistent "Neues Projekt" (js/projekt-wizard.js) ===
|
|
Ersetzt den früheren Dialog #newProjModal, der alle Felder auf einmal
|
|
zeigte. Sechs Schritte, und jeder muss vollständig sein, bevor es
|
|
weitergeht — genau die Lücken, die sonst entstehen (kein Honorar, keine
|
|
Phase, niemand zugeordnet), sind der Grund für Projekte, die später in
|
|
der Auslastung als "nicht bewertbar" erscheinen. */
|
|
.wizard-modal {
|
|
max-width: 820px;
|
|
width: 100%;
|
|
padding: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.wiz-kopf {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 18px 24px 14px;
|
|
border-bottom: 1px solid var(--line-soft);
|
|
}
|
|
.wiz-kopf h2 { margin: 0; flex: 1; }
|
|
.wiz-schliessen {
|
|
font-size: 20px;
|
|
line-height: 1;
|
|
padding: 2px 9px;
|
|
background: none;
|
|
border: none;
|
|
color: var(--text-subtle);
|
|
cursor: pointer;
|
|
}
|
|
.wiz-schliessen:hover { color: var(--text); background: none; }
|
|
|
|
/* Schrittleiste: erledigte Stationen tragen einen Haken, damit auch bei
|
|
sechs Schritten jederzeit erkennbar ist, was noch aussteht. */
|
|
.wiz-leiste {
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
padding: 16px 24px 0;
|
|
}
|
|
.wiz-stufe {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 12px;
|
|
color: var(--text-subtle);
|
|
}
|
|
.wiz-stufe b {
|
|
width: 24px; height: 24px;
|
|
border-radius: 50%;
|
|
display: grid;
|
|
place-items: center;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
background: var(--surface-sunk);
|
|
color: var(--text-muted);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.wiz-stufe.ist-aktiv { color: var(--text); font-weight: 600; }
|
|
.wiz-stufe.ist-aktiv b { background: var(--accent); color: #fff; }
|
|
.wiz-stufe.ist-fertig b { background: var(--green); color: #fff; }
|
|
.wiz-strich {
|
|
flex: 1 1 16px;
|
|
min-width: 10px;
|
|
height: 1px;
|
|
background: var(--line);
|
|
margin: 0 8px;
|
|
}
|
|
|
|
.wiz-inhalt {
|
|
padding: 20px 24px 22px;
|
|
min-height: 300px;
|
|
max-height: 58vh;
|
|
overflow-y: auto;
|
|
}
|
|
.wiz-titel { margin: 0 0 3px; font-size: 19px; font-weight: 600; letter-spacing: -.02em; }
|
|
.wiz-unter { margin: 0 0 20px; font-size: 13px; color: var(--text-muted); }
|
|
.wiz-zeile { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
|
.wiz-pflicht { color: var(--red); }
|
|
|
|
/* Fußzeile: Der Weiter-Knopf bleibt gesperrt, solange etwas fehlt — und
|
|
daneben steht, WAS fehlt. Ein gesperrter Knopf ohne Begründung ist eine
|
|
Sackgasse. */
|
|
.wiz-fuss {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 14px 24px;
|
|
border-top: 1px solid var(--line-soft);
|
|
background: var(--surface-alt);
|
|
}
|
|
.wiz-zaehler { font-size: 12px; color: var(--text-subtle); }
|
|
.wiz-fuss-rechts {
|
|
margin-left: auto;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
flex-wrap: wrap;
|
|
justify-content: flex-end;
|
|
}
|
|
.wiz-fehlt { font-size: 11.5px; color: var(--amber); text-align: right; }
|
|
|
|
/* Auswahl-Chips (Gewerke, Phasen, Schlagwörter, Grundlage) */
|
|
.wiz-chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
.wiz-chip {
|
|
font: inherit;
|
|
font-size: 12px;
|
|
padding: 5px 11px;
|
|
cursor: pointer;
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--r-pill);
|
|
background: var(--surface);
|
|
color: var(--text-muted);
|
|
}
|
|
.wiz-chip:hover { border-color: var(--accent); background: var(--surface); }
|
|
.wiz-chip[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
.wiz-chip-gewerk { font-weight: 600; letter-spacing: .02em; padding: 6px 14px; }
|
|
|
|
/* Beteiligte je Rolle */
|
|
.wiz-rollen { display: flex; flex-direction: column; gap: 12px; }
|
|
.wiz-rolle { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
|
|
.wiz-rolle-name {
|
|
flex: 0 0 116px;
|
|
font-size: 11.5px;
|
|
font-weight: 600;
|
|
padding-top: 7px;
|
|
color: var(--text-muted);
|
|
}
|
|
.wiz-person {
|
|
font: inherit;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
width: 34px; height: 34px;
|
|
border-radius: 50%;
|
|
border: 1px solid var(--line);
|
|
background: var(--surface);
|
|
color: var(--text-muted);
|
|
padding: 0;
|
|
}
|
|
.wiz-person[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
.wiz-person[data-rolle="Projektleitung"][aria-pressed="true"] { background: var(--red); border-color: var(--red); }
|
|
.wiz-person[data-rolle="Bauüberwachung"][aria-pressed="true"] { background: var(--green); border-color: var(--green); }
|
|
/* Wer aus einem anderen Gewerk dazugeholt wurde: gestrichelter Rand, damit
|
|
die Herkunft sichtbar bleibt, ohne eine zweite Farbe einzuführen. */
|
|
.wiz-person.ist-fremd { border-style: dashed; border-width: 1.5px; }
|
|
|
|
.wiz-fremd { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--line-soft); }
|
|
.wiz-fremd label {
|
|
display: block;
|
|
font-size: 11.5px;
|
|
font-weight: 600;
|
|
margin-bottom: 6px;
|
|
color: var(--text-muted);
|
|
}
|
|
.wiz-fremd select { width: 100%; max-width: 420px; }
|
|
.wiz-fremd-liste { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
|
|
.wiz-fremd-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
font-size: 11px;
|
|
padding: 3px 6px 3px 10px;
|
|
border-radius: var(--r-pill);
|
|
border: 1px dashed var(--line);
|
|
color: var(--text-muted);
|
|
}
|
|
.wiz-fremd-chip button {
|
|
font: inherit;
|
|
font-size: 13px;
|
|
line-height: 1;
|
|
cursor: pointer;
|
|
border: none;
|
|
background: none;
|
|
color: var(--text-subtle);
|
|
padding: 0 2px;
|
|
}
|
|
.wiz-fremd-chip button:hover { color: var(--red); background: none; }
|
|
|
|
/* Hinweis anstelle eines Schritts, der gerade nicht möglich ist */
|
|
.wiz-sperre {
|
|
padding: 18px 20px;
|
|
border: 1px dashed var(--line);
|
|
border-radius: var(--r-lg);
|
|
background: var(--surface-alt);
|
|
}
|
|
.wiz-sperre b { display: block; font-size: 14px; margin-bottom: 6px; }
|
|
.wiz-sperre p { margin: 0 0 8px; font-size: 13px; color: var(--text-muted); max-width: 58ch; }
|
|
.wiz-sperre p:last-child { margin-bottom: 0; }
|
|
|
|
.wiz-schalter { display: flex; align-items: center; gap: 9px; font-size: 13.5px; cursor: pointer; }
|
|
.wiz-schalter input { width: 16px; height: 16px; accent-color: var(--accent); }
|
|
|
|
/* Terminplan-Tabelle */
|
|
.wiz-plan {
|
|
margin-top: 16px;
|
|
border: 1px solid var(--line-soft);
|
|
border-radius: var(--r-md);
|
|
overflow: hidden;
|
|
}
|
|
.wiz-plan-zeile {
|
|
display: grid;
|
|
grid-template-columns: 78px 62px 1fr 64px 170px;
|
|
gap: 10px;
|
|
padding: 7px 12px;
|
|
font-size: 12.5px;
|
|
border-bottom: 1px solid var(--line-soft);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.wiz-plan-zeile:last-child { border-bottom: none; }
|
|
.wiz-plan-zeile span:nth-child(2),
|
|
.wiz-plan-zeile span:nth-child(3),
|
|
.wiz-plan-zeile span:nth-child(4) { text-align: right; }
|
|
.wiz-plan-kopf {
|
|
background: var(--surface-alt);
|
|
font-size: 10.5px;
|
|
font-weight: 600;
|
|
letter-spacing: .05em;
|
|
text-transform: uppercase;
|
|
color: var(--text-subtle);
|
|
}
|
|
.wiz-plan-summe { background: var(--surface-alt); font-weight: 600; }
|
|
|
|
/* Zusammenfassung */
|
|
.wiz-pruef { font-size: 13.5px; }
|
|
.wiz-pruef-zeile {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
gap: 18px;
|
|
padding: 7px 0;
|
|
border-bottom: 1px solid var(--line-soft);
|
|
}
|
|
.wiz-pruef-zeile:last-child { border-bottom: none; }
|
|
.wiz-pruef-zeile span { color: var(--text-muted); flex: 0 0 auto; }
|
|
.wiz-pruef-zeile b { font-weight: 500; text-align: right; min-width: 0; overflow-wrap: anywhere; }
|
|
.wiz-pruef-zeile b.fehlt { color: var(--text-subtle); font-weight: 400; font-style: italic; }
|
|
|
|
/* Abschluss */
|
|
.wiz-fertig { text-align: center; padding: 44px 24px; }
|
|
.wiz-haken {
|
|
width: 52px; height: 52px;
|
|
border-radius: 50%;
|
|
margin: 0 auto 16px;
|
|
display: grid;
|
|
place-items: center;
|
|
font-size: 26px;
|
|
background: var(--green);
|
|
color: #fff;
|
|
}
|
|
.wiz-fertig h3 { margin: 0 0 8px; font-size: 20px; font-weight: 600; }
|
|
.wiz-fertig p { margin: 0 auto 20px; color: var(--text-muted); max-width: 46ch; font-size: 13.5px; }
|
|
|
|
@media (max-width: 760px) {
|
|
.wiz-zeile { grid-template-columns: 1fr; }
|
|
.wiz-stufe span { display: none; }
|
|
.wiz-plan-zeile { grid-template-columns: 66px 1fr 130px; }
|
|
.wiz-plan-zeile span:nth-child(2),
|
|
.wiz-plan-zeile span:nth-child(4) { display: none; }
|
|
}
|
|
|
|
/* ============================================================
|
|
BEWEGUNG - Stufe "Spürbar"
|
|
============================================================
|
|
Von Erik aus einem Dreier-Vergleich gewählt (dezent / spürbar /
|
|
lebendig). Die Werte stehen als Tokens oben in :root; hier wird
|
|
nur angewendet.
|
|
|
|
Bewusst NICHT enthalten: Eintritts-Animationen für Listen und
|
|
aufziehende Balken (das war Stufe 3). Die Oberfläche bewegt sich
|
|
ausschliesslich als Antwort auf Zeigen oder Klicken, nie von
|
|
selbst - bei einer Liste mit dreissig Projekten, die mehrmals
|
|
täglich neu gerendert wird, wäre alles andere Unruhe.
|
|
|
|
Die Wandtafel (body.wandtafel-aktiv) bleibt bewusst aussen vor:
|
|
ein Touch-Monitor kennt kein Hover, und die Tafel trägt ohnehin
|
|
ihre eigene Gestaltung (--dash-*).
|
|
============================================================ */
|
|
|
|
/* --- 1. Anklickbare Zeilen: anheben + Akzentleiste ---------------
|
|
Die Leiste entsteht als inset-Schatten, NICHT als ::before mit
|
|
position:absolute. Grund: .board-row ist draggable (Drag-and-Drop
|
|
der Projektliste, js/projects/projects.js). Ein zusätzliches
|
|
positioniertes Element bzw. ein Stapelkontext auf der Zeile kann
|
|
das Ziehbild und die Trefferflächen verschieben; ein inset-Schatten
|
|
kann das nicht, weil er weder Layout noch Stapelkontext anfasst.
|
|
Der Schatten wächst von 0 auf 3px, wirkt also wie eine von links
|
|
einfahrende Leiste. */
|
|
.board-row-main,
|
|
.bericht-rail-item,
|
|
.planung-row,
|
|
.dash-rand-zeile {
|
|
transition: background var(--dur) var(--ease-out),
|
|
box-shadow var(--dur) var(--ease-out),
|
|
transform var(--dur) var(--ease-out);
|
|
}
|
|
.board-row-main:hover,
|
|
.bericht-rail-item:hover,
|
|
.planung-row:hover,
|
|
.dash-rand-zeile:hover {
|
|
transform: translateY(var(--row-lift));
|
|
box-shadow: inset 3px 0 0 0 var(--accent);
|
|
}
|
|
/* Beim Drücken wieder absetzen: die Zeile geht unter den Finger
|
|
zurück, statt angehoben zu bleiben. */
|
|
.board-row-main:active,
|
|
.bericht-rail-item:active,
|
|
.planung-row:active,
|
|
.dash-rand-zeile:active {
|
|
transform: translateY(0);
|
|
transition-duration: 60ms;
|
|
}
|
|
/* Eine bereits ausgewählte Zeile trägt die Leiste dauerhaft - sonst
|
|
verschwände beim Wegfahren der Maus die einzige Markierung. */
|
|
.bericht-rail-item.is-aktiv { box-shadow: inset 3px 0 0 0 var(--accent); }
|
|
|
|
/* --- 2. Druckpunkt für alles Klickbare -------------------------
|
|
button:active trug schon scale(.97); hier kommen die übrigen
|
|
Bedienelemente auf denselben Wert, damit sich nicht das eine
|
|
Element gedrückt anfühlt und das Nachbarelement nicht. */
|
|
.board-avatar,
|
|
.person-chip,
|
|
.nav-item,
|
|
.attendance-tile,
|
|
.plan-verweis,
|
|
.tag-chip .tag-remove,
|
|
.richtext-btn,
|
|
.pc-btn {
|
|
transition: background var(--dur) var(--ease-out),
|
|
border-color var(--dur) var(--ease-out),
|
|
color var(--dur) var(--ease-out),
|
|
transform var(--dur) var(--ease-out);
|
|
}
|
|
.board-avatar:not(.is-ansicht):active,
|
|
.person-chip:active,
|
|
.nav-item:active,
|
|
.attendance-tile:active,
|
|
.plan-verweis:active,
|
|
.richtext-btn:active,
|
|
.pc-btn:active { transform: scale(var(--press)); }
|
|
|
|
/* --- 3. Dialoge: auftauchen statt aufblitzen -------------------
|
|
.modal-bg wird per JS von display:none auf flex geschaltet. Ein
|
|
display-Wechsel ist nicht übergangsfähig, eine ANIMATION dagegen
|
|
startet neu, sobald das Element wieder gerendert wird - deshalb
|
|
hier animation statt transition. Die Fläche blendet auf, der
|
|
Dialog kommt dabei minimal aus der Tiefe. */
|
|
.modal-bg { animation: modal-bg-auf var(--dur-lg) var(--ease-out); }
|
|
.modal-bg > .modal { animation: modal-auf var(--dur-lg) var(--ease-out); }
|
|
@keyframes modal-bg-auf { from { opacity: 0; } to { opacity: 1; } }
|
|
@keyframes modal-auf {
|
|
from { opacity: 0; transform: translateY(6px) scale(.985); }
|
|
to { opacity: 1; transform: none; }
|
|
}
|
|
|
|
/* --- 4. Tastatur-Fokus sichtbar halten ------------------------
|
|
Die Hebe-/Druck-Effekte oben reagieren auf Maus und Finger. Wer
|
|
mit der Tastatur arbeitet, braucht eine eigene, deutliche Marke;
|
|
ohne sie wäre die Oberfläche nach diesem Umbau für Tastatur-
|
|
Bedienung schlechter lesbar als vorher. */
|
|
.board-row-main:focus-visible,
|
|
.bericht-rail-item:focus-visible,
|
|
.planung-row:focus-visible,
|
|
.person-chip:focus-visible,
|
|
.board-avatar:focus-visible,
|
|
.nav-item:focus-visible {
|
|
outline: 2px solid var(--accent);
|
|
outline-offset: -2px;
|
|
}
|
|
|
|
/* --- 5. Bewegung abbestellt ------------------------------------
|
|
Pflichtteil, in dieser Datei bisher gar nicht vorhanden. Weil die
|
|
Stufe an Variablen hängt, genügt es, sie zu kippen: ohne Hub und
|
|
ohne Stauchung bleibt reine Farb-Rückmeldung übrig. Die
|
|
transition-/animation-Zeilen fangen zusätzlich die rund sechzig
|
|
älteren Regeln ab, die ihre Dauer noch fest eingetragen haben. */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
:root { --row-lift: 0px; --press: 1; --dur: 1ms; --dur-lg: 1ms; }
|
|
*, *::before, *::after {
|
|
animation-duration: 1ms !important;
|
|
animation-iteration-count: 1 !important;
|
|
transition-duration: 1ms !important;
|
|
scroll-behavior: auto !important;
|
|
}
|
|
}
|