/* === 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 (js/wand/wand.js) === Zweispaltiges Touch-Dashboard für ein dauerhaft laufendes Display, gewerke- und nutzerübergreifend — ersetzt den früheren Show Mode, der nur die gerade angemeldete Sitzung großflächig klonte (keine Aggregation über Gewerke oder Nutzer hinweg, siehe CLAUDE.md). Seitenleiste und Kopfzeile blendet showTab() über body.wandtafel-aktiv aus: Das Kiosk-Konto ist ein gewöhnliches Mitgliedskonto mit echten Schreibrechten — die Kachelfläche soll die einzige Berührfläche sein, damit eine zufällige Berührung nicht in einen bearbeitbaren Bereich wie "Projekte" führt. Verhält sich zusätzlich wie ein fester Bildschirmausschnitt statt einer normal scrollenden Seite (height:100vh + overflow:hidden auf dem Weg .app-main → .container → #wand) — ein unbeaufsichtigtes Display soll von selbst nie scrollen müssen; die zwei Spalten strecken sich über die volle restliche Höhe. AUSNAHME, auf Eriks Wunsch (echter Touch-Monitor, "ich kann mich vor die Tafel stellen und scrollen"): unterhalb der zwei Spalten folgt die vollständige Projektliste (wandProjektlisteHtml(), js/wand/wand.js), und NUR dieser Bereich (#wandContent, Klasse .wand-scrollable, von renderWandboard() gesetzt) scrollt — eine offene Übersicht bleibt weiter exakt einen Bildschirm hoch, siehe .wand-uebersicht unten. Ohne Berührung kehrt die Ansicht nach WAND_IDLE_MS von selbst an den Anfang zurück (wandAktivitaetErkannt()/wandBeiUntaetigkeitZuruecksetzen()). */ body.wandtafel-aktiv .app-topbar, body.wandtafel-aktiv .side-menu, body.wandtafel-aktiv .side-menu-backdrop { display: none !important; } body.wandtafel-aktiv .app-main { height: 100vh; overflow: hidden; } body.wandtafel-aktiv .container { height: 100%; overflow: hidden; display: flex; flex-direction: column; padding: 14px 20px; } body.wandtafel-aktiv #wand { display: flex !important; flex-direction: column; flex: 1; min-height: 0; } .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; } .wand-grid-wrap.wand-scrollable { display: block; overflow-y: auto; } .wand-ambient { min-height: 100%; display: flex; flex-direction: column; } .wand-c-layout { flex: 1; min-height: 0; display: grid; grid-template-columns: 320px 1fr; gap: 16px; } .wand-c-links { display: flex; flex-direction: column; gap: 16px; min-height: 0; } .wand-c-links .wand-kachel { flex: 1; min-height: 0; } .wand-c-rechts { background: var(--surface); border: 1px solid var(--line-soft); border-radius: var(--r-xl); box-shadow: var(--sh-2); padding: 18px 22px; display: flex; flex-direction: column; min-height: 0; } .wand-kachel { background: var(--surface); border: 1px solid var(--line-soft); border-radius: var(--r-xl); box-shadow: var(--sh-2); padding: 18px 20px; display: flex; flex-direction: column; min-height: 0; } .wand-kachel-titel { margin: 0 0 12px; font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); flex-shrink: 0; } /* Der Inhalt verteilt sich GLEICHMÄSSIG über die volle, gestreckte Kachelhöhe (space-evenly) statt oben zu kleben und unten Leerraum zu lassen — genau das ließ vorher dünn befüllte Kacheln neben vollen leer wirken. */ .wand-kachel-body { flex: 1; display: flex; flex-direction: column; justify-content: space-evenly; min-height: 0; overflow: hidden; } /* Meter-Zeilen: Auslastung je Gewerk, Wochenbericht-Fortschritt je Gewerk. Nicht mehr die Kachel als Ganzes ist antippbar, sondern jede einzelne Gewerk-Zeile für sich (wandZeigeUebersicht('auslastung'|'besprochen', g)) — deshalb trägt .ist-klickbar hier die Zeile, nicht mehr .wand-kachel. */ .wand-meter-zeile { display: flex; align-items: center; gap: 12px; } .wand-meter-zeile.ist-klickbar { cursor: pointer; border-radius: var(--r-md); margin: 0 -6px; padding: 3px 6px; transition: background .15s var(--ease); } .wand-meter-zeile.ist-klickbar:hover { background: var(--surface-sunk); } .wand-meter-label { flex: 0 0 90px; font-size: 13px; font-weight: 600; color: var(--text); } .wand-meter { flex: 1; height: 18px; background: var(--surface-sunk); border-radius: var(--r-pill); overflow: hidden; display: flex; align-items: center; } .wand-meter-fill { height: 100%; border-radius: var(--r-pill); transition: width .3s var(--ease); } .wand-meter-leer { font-size: 12px; color: var(--text-subtle); padding-left: 4px; } .wand-meter-wert { flex: 0 0 76px; text-align: right; font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums; } .wand-einheit { font-size: 11px; font-weight: 400; color: var(--text-subtle); margin-left: 2px; } .wand-leer-klein { font-size: 11.5px; color: var(--text-subtle); } /* Kennzahlen: Kachel "Heute" (offene/überfällige Aufgaben, Abwesend) — jede Kennzahl und jedes Kürzel ist für sich antippbar (.ist-klickbar), öffnet die passende Vollbild-Übersicht. */ .wand-stat-reihe { display: flex; gap: 28px; } .wand-stat { display: flex; flex-direction: column; gap: 2px; } .wand-stat b { font-size: 30px; font-weight: 700; letter-spacing: -.02em; font-variant-numeric: tabular-nums; } .wand-stat span { font-size: 12px; color: var(--text-subtle); } .wand-stat.ist-rot b { color: var(--red); } .wand-stat.ist-klickbar { cursor: pointer; } .wand-stat.ist-klickbar:hover b { text-decoration: underline; } .wand-namen { display: flex; flex-wrap: wrap; gap: 6px; } .wand-avatar { width: 28px; height: 28px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: 10.5px; font-weight: 700; background: var(--accent-soft); color: var(--accent); } .wand-avatar.ist-klickbar { cursor: pointer; } .wand-avatar.ist-klickbar:hover { box-shadow: 0 0 0 2px var(--accent); } .wand-leer { font-size: 13px; color: var(--text-subtle); } /* Ereignis-Feed (rechte Spalte) — auf Eriks Wahl "Variante C" aus dem Design-Vergleich: drei Spalten nach Art (Neu/Überfällig/Termine) statt einer gemischten, chronologisch sortierten Liste. Zeigt auf einen Blick, WIE VIELE Ereignisse welcher Art anstehen — kostet dafür die vorherige "zeitlich am nächsten zuerst"-Durchmischung über die Arten hinweg; innerhalb einer Spalte sortiert wandAktuelleEreignisse() unverändert nach zeitlicher Nähe. Jeder Eintrag bleibt für sich antippbar und springt direkt ins betroffene Projekt. */ .wand-feed-wrap { flex: 1; min-height: 0; overflow: hidden; } .wand-feed-gruppen { display: flex; gap: 16px; height: 100%; } .wand-feed-gruppe { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; overflow: hidden; } .wand-feed-gruppe-kopf { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; flex-shrink: 0; } .wand-feed-gruppe-punkt { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; } .wand-feed-gruppe-punkt.neu { background: var(--accent); } .wand-feed-gruppe-punkt.ueberfaellig { background: var(--red); } .wand-feed-gruppe-punkt.termin { background: var(--amber); } .wand-feed-gruppe-titel { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); } .wand-feed-gruppe-anzahl { font-size: 10.5px; color: var(--text-subtle); margin-left: auto; } .wand-feed-eintrag { font-size: 12px; line-height: 1.4; padding: 8px 0; border-top: 1px solid var(--line-soft); cursor: pointer; border-radius: var(--r-md); transition: background .15s var(--ease); } .wand-feed-eintrag:first-of-type { border-top: none; padding-top: 0; } .wand-feed-eintrag:hover { background: var(--surface-sunk); } .wand-feed-eintrag b { display: block; font-weight: 600; color: var(--text); margin-bottom: 1px; } .wand-feed-eintrag span { color: var(--text-muted); } .wand-feed-zeit { display: block; font-size: 10.5px; color: var(--text-subtle); margin-top: 2px; } .wand-stand { text-align: center; font-size: 11px; color: var(--text-subtle); margin-top: 10px; flex-shrink: 0; } /* Vollbild-Übersichten (überfällige/offene Aufgaben, Auslastung/Besprochen je Gewerk, Abwesenheiten) — ersetzen das zweispaltige Layout 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. */ .wand-uebersicht { flex: 1; min-height: 0; display: flex; flex-direction: column; background: var(--surface); border: 1px solid var(--line-soft); border-radius: var(--r-xl); box-shadow: var(--sh-2); padding: 20px 24px; } .wand-uebersicht-kopf { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; flex-shrink: 0; } .wand-uebersicht-kopf h2 { margin: 0; font-size: 19px; font-weight: 700; letter-spacing: -.01em; } .wand-uebersicht-liste { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; } .wand-uebersicht-zeile { display: flex; align-items: center; gap: 18px; padding: 13px 6px; border-top: 1px solid var(--line-soft); transition: background .15s var(--ease); } .wand-uebersicht-zeile:first-child { border-top: none; } .wand-uebersicht-zeile.ist-klickbar { cursor: pointer; } .wand-uebersicht-zeile.ist-klickbar:hover { background: var(--surface-sunk); } .wand-uebersicht-projekt { flex: 0 0 280px; font-weight: 600; font-size: 14.5px; color: var(--text); } .wand-uebersicht-titel { flex: 1; font-size: 14.5px; color: var(--text); } /* Neutral, nicht mehr fest rot: eine Übersichts-Zeile kann auch eine unauffällige Angabe zeigen (Stunden, "noch offen", Abwesenheitsende) — .ist-rot markiert gezielt die wirklich überfälligen Zeilen. */ .wand-uebersicht-seit { flex-shrink: 0; font-size: 13px; font-weight: 600; color: var(--text-muted); } .wand-uebersicht-seit.ist-rot { color: var(--red); } /* Vollständige Projektliste unterhalb der zwei Spalten (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