/* === 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); } /* === 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 -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
, kein