monitor/html/css/office.css
fds2610-bot 9ce938c45b Kanban-Board: Ticket-Nummer auf den einzelnen Zetteln anzeigen (nicht am Stapel)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 15:05:42 +02:00

964 lines
19 KiB
CSS

/* Buero-Visualisierung (Phase 2, statische Szene). Bewusst ohne UI-Framework, siehe
Visualization.md Abschnitt 5. Farbschema hell/freundlich (F9: "Zeichentrick"-Anmutung). */
:root {
--color-floor: #eef2f7;
--color-wall: #cfe0f0;
--color-wall-side: #bcd3ea;
--color-door: #8fb8dd;
--color-board-frame: #94622f;
--color-note: #fde68a;
--color-note-border: #d9b34f;
--color-note-stack-badge: #d97706;
--color-recording-badge: #dc2626;
--color-monitor-frame: #2b2f38;
--color-monitor-screen: #10131a;
--color-monitor-placeholder: #1a2436;
--color-desk-table: #b98a54;
--color-desk-chair: #6b7280;
--color-desk-screen-idle: #3a3f4b;
--color-text: #1f2937;
--color-text-inverse: #f8fafc;
}
* {
box-sizing: border-box;
}
/* Die Szene soll immer vollstaendig sichtbar sein - auch im normalen Fenstermodus,
ohne Scrollen. Dazu spannt der body eine Flex-Spalte ueber die Viewport-Hoehe auf:
Header nimmt seine natuerliche Hoehe, #office-scene bekommt den Rest, und das SVG
skaliert sich per aspect-ratio in diesen Rest hinein (siehe unten). */
html {
height: 100%;
}
body {
margin: 0;
font-family: "Segoe UI", Verdana, Arial, sans-serif;
background: #dbe7f3;
color: var(--color-text);
/* height (nicht min-height): Nur eine definite Hoehe laesst die prozentualen
max-height-Angaben weiter unten aufloesen. dvh rechnet mobile Browserleisten
mit, vh ist der Fallback fuer aeltere Engines. */
height: 100vh;
height: 100dvh;
display: flex;
flex-direction: column;
overflow: hidden;
}
header.office-header {
flex: 0 0 auto;
padding: 12px 24px;
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 16px;
}
header.office-header h1 {
margin: 0;
font-size: 1.4rem;
}
/* Rein kosmetischer Countdown bis zum naechsten Codeberg-Sync (Issue #12), rechtsbuendig
anstelle des frueheren erklaerenden Fliesstexts. Tabular-nums verhindert Wackeln der
Breite, wenn sich die Ziffernzahl aendert (z.B. 9s -> 10s). */
.reload-countdown {
color: #475569;
font-size: 0.9rem;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
/* Warnbanner, wenn die API nicht erreichbar ist und die Szene auf Demo-Daten zurueckfaellt
(siehe setApiBanner() in js/office-render.js). */
.api-banner {
flex: 0 0 auto;
padding: 10px 24px;
background: #dc2626;
color: #ffffff;
font-weight: 700;
text-align: center;
letter-spacing: 0.02em;
}
/* Muss nach der Regel oben stehen: ohne diese Zeile wuerde das hidden-Attribut vom
flex-Wert ueberstimmt und der Banner waere dauerhaft sichtbar. */
.api-banner[hidden] {
display: none;
}
/* Reine Vorwarnung (Anmeldung laeuft demnaechst ab, der Agent arbeitet noch): gelb statt rot,
damit das Rot dem tatsaechlichen Stillstand vorbehalten bleibt. Dunkle Schrift, weil Weiss
auf Gelb kaum zu lesen ist. */
.api-banner.api-banner-warning {
background: #facc15;
color: #422006;
}
#office-scene {
flex: 1 1 auto;
/* min-height: 0 hebt die Flex-Default-Mindesthoehe (min-content) auf - sonst kann
das Kind den Container nicht unterschreiten und die Szene laeuft aus dem Bild. */
min-height: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 0 16px 16px;
}
/* Das SVG soll sich an der jeweils knapperen Achse ausrichten. Welche das ist, haengt
vom Seitenverhaeltnis des Fensters ab - daher die beiden Media-Queries unten. Die
max-*-Grenzen sind das Sicherheitsnetz fuer den Grenzbereich (die Header-Hoehe geht
in das Verhaeltnis der Szenenflaeche ein, in die Media-Query aber nicht): Dort wird
die Elementbox notfalls leicht gestaucht - der Szeneninhalt bleibt dank
preserveAspectRatio unverzerrt und vollstaendig sichtbar. */
.office-svg {
max-width: 100%;
max-height: 100%;
aspect-ratio: 1920 / 1080;
display: block;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.25);
}
/* Fenster breiter als 16:9 -> die Hoehe ist der Engpass. */
@media (min-aspect-ratio: 16 / 9) {
.office-svg {
height: 100%;
width: auto;
}
}
/* Fenster schmaler als 16:9 -> die Breite ist der Engpass. */
@media (max-aspect-ratio: 16 / 9) {
.office-svg {
width: 100%;
height: auto;
}
}
@media (max-height: 640px) {
header.office-header {
padding: 8px 16px;
}
header.office-header h1 {
font-size: 1.1rem;
}
}
.floor {
fill: var(--color-floor);
}
.wall {
fill: var(--color-wall);
}
.wall-side {
fill: var(--color-wall-side);
}
.door {
fill: var(--color-door);
stroke: #5f86ab;
stroke-width: 3;
transition: fill 0.4s ease-in-out;
}
/* Phase 6 (F2): kurzes Aufleuchten, waehrend ein "user"-Tuerbesuch die Tuer passiert. */
.door-active {
fill: #d7ecff;
}
.wall-label {
fill: #f8fafc;
font-size: 22px;
font-weight: 600;
}
/* Kanban-Board */
.board-frame {
fill: #7a5230;
stroke: var(--color-board-frame);
stroke-width: 6;
}
.board-column-label {
fill: var(--color-text-inverse);
font-size: 16px;
font-weight: 600;
}
.board-column-divider {
stroke: rgba(255, 255, 255, 0.25);
stroke-width: 2;
}
.note {
fill: var(--color-note);
stroke: var(--color-note-border);
stroke-width: 2;
}
/* Zettel in der Farbe seines Projekts (Issue #27): die Fuellung kommt als Inline-Style aus
der Projektkonfiguration, die Kontur muss dann neutral sein - die gelbe Standardkontur
passt nur zum gelben Standardzettel. */
.note-colored {
stroke: rgba(15, 23, 42, 0.35);
}
/* Issue #91: Ticket-Nummer auf dem Zettel; heller Rand haelt sie auf dunklen Projektfarben lesbar. */
.note-number {
fill: #0f172a;
font-size: 16px;
font-weight: 700;
paint-order: stroke;
stroke: rgba(255, 255, 255, 0.8);
stroke-width: 3px;
pointer-events: none;
}
.note-stack-badge {
fill: var(--color-note-stack-badge);
stroke: #92400e;
stroke-width: 2;
}
.note-stack-count {
fill: #ffffff;
font-size: 20px;
font-weight: 700;
}
/* Monitor-Wand */
.monitor-frame {
fill: var(--color-monitor-frame);
}
.monitor-screen {
fill: var(--color-monitor-screen);
}
.monitor-screen-placeholder {
fill: var(--color-monitor-placeholder);
}
.monitor-event-line {
font-family: "Consolas", "Courier New", monospace;
font-size: 13px;
/* Grundfarbe fuer Schweregrade ohne eigene Farbe (z.B. info) - ohne sie stuende der Text
schwarz auf dem dunklen Monitor. */
fill: #cbd5e1;
}
/* Kopfzeile eines Events (Zeit + System): tritt gegenueber der Meldung zurueck. */
.monitor-event-meta {
opacity: 0.75;
}
.severity-warning,
.severity-warning .monitor-event-line {
fill: #facc15;
}
.severity-error,
.severity-error .monitor-event-line {
fill: #f87171;
}
.system-tile-bg {
fill: #1e293b;
}
.system-tile-mark {
fill: none;
stroke: #64748b;
stroke-linecap: round;
stroke-linejoin: round;
}
.status-ok .system-tile-mark {
stroke: #4ade80;
}
.status-error .system-tile-mark {
stroke: #f87171;
}
/* Servername rechts neben der Kachel. Monospace und klein genug, dass auch lange Namen in
einer Zeile neben die Kachel passen (gekuerzt wird in renderSystemMonitor). */
.system-tile-name {
font-family: "Consolas", "Courier New", monospace;
font-size: 11px;
fill: #cbd5e1;
dominant-baseline: central;
}
.status-error .system-tile-name {
fill: #f87171;
}
.monitor-placeholder-text {
fill: #64748b;
font-size: 15px;
}
/* Schreibtische */
.desk-table {
fill: var(--color-desk-table);
stroke: #8a6238;
stroke-width: 3;
}
.desk-screen {
fill: var(--color-desk-screen-idle);
stroke: #1f2430;
stroke-width: 2;
}
.desk-screen.pose-working {
fill: #7dd3fc;
}
/* Blockierter Platz (Claude-Anmeldung abgelaufen): der Bildschirm traegt den Zustand mit,
damit er auch dann am Platz sichtbar bleibt, wenn der Avatar gerade nicht dort steht. */
.desk-screen.pose-auth_expired {
fill: #7f1d1d;
stroke: #f87171;
}
.desk-screen.pose-auth_expiring {
fill: #713f12;
stroke: #facc15;
}
.desk-chair {
fill: var(--color-desk-chair);
}
/* Zaehler offener Sprachaufnahmen (Issue #85), oben links am Schreibtisch. */
.recording-badge-circle {
fill: var(--color-recording-badge);
stroke: #7c2d12;
stroke-width: 2;
}
.recording-badge-count {
fill: #ffffff;
font-size: 13px;
font-weight: 700;
}
.person-head {
fill: #f3d9b1;
stroke: #d8b98a;
stroke-width: 1.5;
}
.person-body {
fill: var(--role-color, #94a3b8);
}
.desk-name {
fill: #334155;
font-size: 14px;
font-weight: 600;
}
.desk-name-empty {
fill: #94a3b8;
font-style: italic;
font-weight: 400;
}
/* Der Name eines Agenten am Schreibtisch steht am Tisch, nicht am Avatar (updateAgentsLayer()
blendet .avatar-name am Platz aus) - der Problemzustand faerbt deshalb hier. */
.desk-name.desk-name-problem-error {
fill: #b91c1c;
font-weight: 700;
}
.desk-name.desk-name-problem-warning {
fill: #a16207;
font-weight: 700;
}
.desk-deco-star {
fill: #fbbf24;
}
.desk-deco-flower circle {
fill: #f9a8d4;
}
.desk-deco-flower-center {
fill: #fde047 !important;
}
.desk-deco-check {
fill: none;
stroke: #22c55e;
stroke-width: 4;
stroke-linecap: round;
stroke-linejoin: round;
}
.desk-deco-gear circle,
.desk-deco-gear rect {
fill: #a78bfa;
}
/* Heller Kern, damit der gefuellte Kreis als Zahnrad-Ring lesbar wird (wie beim Bluetenkern). */
.desk-deco-gear-center {
fill: #ede9fe !important;
}
/* Mikrofon am Schreibtisch des Voice-Bots: gefuellte Kapsel, Buegel und Fuss nur als Strich -
sonst wuerde der Halbkreis als Flaeche zulaufen. */
.desk-deco-mic-capsule {
fill: #5eead4;
}
.desk-deco-mic-bracket {
fill: none;
stroke: #5eead4;
stroke-width: 2.5;
stroke-linecap: round;
}
.desk-empty .desk-chair {
opacity: 0.5;
}
/* Klickbare Bereiche (Phase 3/4: Board-Spalten + Monitore oeffnen ein Modal) */
.clickable {
cursor: pointer;
}
.board-column-hit {
fill: transparent;
pointer-events: all;
}
/* Trefferflaeche des PO-Platzes (Issue #27): macht den ganzen Platz klickbar, nicht nur die
gefuellten Moebelstuecke - ein Klick daneben landete sonst auf dem Boden. */
.po-desk-hit {
fill: transparent;
pointer-events: all;
}
/* Konfig-Zahnrad (Issue #27 am PO-Platz, Issue #70 am Systeme-Monitor): zeigt ueberhaupt
erst, dass sich hier etwas einstellen laesst. Aussehen ist ueberall gleich, nur der
Ausloeser des Hovers unterscheidet sich - beim PO der ganze Schreibtisch, beim Monitor
das Symbol selbst (der Monitor traegt schon eine eigene Klick-Aktion). */
.config-hint-bg {
fill: #ffffff;
stroke: #94a3b8;
stroke-width: 1.5;
opacity: 0.85;
}
.config-hint-tooth,
.config-hint-hub {
stroke: #475569;
stroke-width: 2;
stroke-linecap: round;
fill: none;
}
.po-desk:hover .config-hint-bg,
.system-config-hint:hover .config-hint-bg {
fill: #e0f2fe;
stroke: #0284c7;
opacity: 1;
}
.po-desk:hover .config-hint-tooth,
.po-desk:hover .config-hint-hub,
.system-config-hint:hover .config-hint-tooth,
.system-config-hint:hover .config-hint-hub {
stroke: #0284c7;
}
.system-config-hint-hit {
fill: transparent;
pointer-events: all;
}
/* Reload-Symbol neben der Board-Ueberschrift (Issue #51): stoesst ein komplettes
Neueinlesen des Kanban-Boards an, analog zum Zahnrad am PO-Platz. */
.board-reload-hint-bg {
fill: #ffffff;
stroke: #94a3b8;
stroke-width: 1.5;
opacity: 0.85;
}
.board-reload-hint-arrow {
fill: none;
stroke: #475569;
stroke-width: 2;
stroke-linecap: round;
}
.board-reload-hint-arrowhead {
fill: #475569;
}
.board-reload-hint-hit {
fill: transparent;
pointer-events: all;
}
.board-reload-hint:hover .board-reload-hint-bg {
fill: #e0f2fe;
stroke: #0284c7;
opacity: 1;
}
.board-reload-hint:hover .board-reload-hint-arrow {
stroke: #0284c7;
}
.board-reload-hint:hover .board-reload-hint-arrowhead {
fill: #0284c7;
}
.just-updated .board-frame,
.just-updated .monitor-frame {
animation: office-flash 0.9s ease-out;
}
@keyframes office-flash {
0% {
filter: drop-shadow(0 0 0 rgba(250, 204, 21, 0));
}
25% {
filter: drop-shadow(0 0 12px rgba(250, 204, 21, 0.9));
}
100% {
filter: drop-shadow(0 0 0 rgba(250, 204, 21, 0));
}
}
/* Kaffee-Ecke (Phase 5, Raum-Prop zwischen den Schreibtischreihen) */
.coffee-table {
fill: #8a6238;
stroke: #6b4a28;
stroke-width: 2;
}
.coffee-icon {
font-size: 20px;
}
/* Agenten-Avatare (Phase 5: bewegliche Schicht, siehe office-render.js) */
.agent-avatar {
transition: transform 1.1s ease-in-out;
}
.agent-avatar .person-head {
fill: #f3d9b1;
stroke: #d8b98a;
stroke-width: 1.5;
}
.agent-avatar .person-body {
fill: var(--role-color, #94a3b8);
}
.agent-avatar.pose-walking .person-leg {
fill: #475569;
transform-box: fill-box;
transform-origin: top center;
animation: office-leg-swing 0.6s ease-in-out infinite alternate;
}
.agent-avatar.pose-walking .person-leg-right {
animation-delay: 0.3s;
}
@keyframes office-leg-swing {
from {
transform: rotate(-18deg);
}
to {
transform: rotate(18deg);
}
}
/* Blockierter Agent (AGENT_PROBLEM_STATES): der Koerper verliert seine Rollenfarbe, damit die
Figur als "steht still" lesbar wird, ohne aus der Szene zu verschwinden. */
.agent-avatar.pose-auth_expired .person-body,
.agent-avatar.pose-auth_expiring .person-body {
fill: #94a3b8;
}
.person-prop-lock-body {
fill: #f87171;
stroke: #7f1d1d;
stroke-width: 1.5;
}
.person-prop-lock-shackle {
fill: none;
stroke: #f87171;
stroke-width: 3;
stroke-linecap: round;
}
.pose-auth_expiring .person-prop-lock-body {
fill: #facc15;
stroke: #713f12;
}
.pose-auth_expiring .person-prop-lock-shackle {
stroke: #facc15;
}
/* Nur der harte Ausfall pulsiert - eine pulsierende Vorwarnung waere auf einem Dauermonitor
Rauschen, das man nach einer Stunde nicht mehr sieht. */
.agent-avatar.pose-auth_expired .person-prop-lock {
animation: office-lock-pulse 1.4s ease-in-out infinite;
}
@keyframes office-lock-pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.35;
}
}
.person-prop-cup {
fill: #f8fafc;
stroke: #94a3b8;
}
.person-prop-newspaper {
fill: #f8fafc;
stroke: #94a3b8;
stroke-width: 1;
}
.person-prop-speech {
fill: #f8fafc;
stroke: #94a3b8;
stroke-width: 1;
}
.avatar-name {
fill: #334155;
font-size: 13px;
font-weight: 600;
text-anchor: middle;
}
/* KPI-Sparklines (Phase 4, F10: kein Chart-Lib, eigenes SVG-Zeichnen) */
.kpi-tile-label {
fill: var(--color-text-inverse);
font-size: 13px;
font-weight: 600;
}
/* Kombiniert notiert, weil .kpi-tile-label weiter unten steht als .severity-* und die Farbe
sonst bei gleicher Spezifitaet ueber die Reihenfolge zurueckgewinnt. */
.kpi-tile-label.severity-error {
fill: #f87171;
}
.kpi-tile-label.severity-warning {
fill: #facc15;
}
.kpi-sparkline-bg {
fill: rgba(255, 255, 255, 0.04);
}
.kpi-sparkline-line {
fill: none;
stroke: #7dd3fc;
stroke-width: 2;
stroke-linejoin: round;
stroke-linecap: round;
}
/* Modal (Phase 3/4: Board-/Monitor-Detailansicht, Visualization.md 4.5) */
.office-modal {
border: none;
border-radius: 12px;
padding: 0;
width: min(640px, 92vw);
max-height: 80vh;
box-shadow: 0 20px 50px rgba(15, 23, 42, 0.4);
}
.office-modal::backdrop {
background: rgba(15, 23, 42, 0.55);
}
.office-modal-form {
display: flex;
flex-direction: column;
max-height: 80vh;
}
.office-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid #e2e8f0;
}
.office-modal-title {
margin: 0;
font-size: 1.1rem;
}
.office-modal-close {
border: none;
background: transparent;
font-size: 1.4rem;
line-height: 1;
cursor: pointer;
color: #64748b;
}
.office-modal-body {
padding: 16px 20px;
overflow-y: auto;
}
.office-modal-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 10px;
}
.office-modal-item {
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 10px 12px;
}
.office-modal-item p {
margin: 6px 0 0;
color: #475569;
font-size: 0.9rem;
}
.office-modal-item .office-modal-description {
white-space: pre-line;
}
.office-modal-repo {
color: #64748b;
font-size: 0.85rem;
}
.office-modal-item-heading {
flex-wrap: wrap;
display: flex;
align-items: center;
gap: 8px;
}
.office-modal-badge {
font-size: 0.75rem;
padding: 2px 8px;
border-radius: 999px;
background: #e2e8f0;
color: #1f2937;
}
.office-modal-item.severity-error {
border-color: #fca5a5;
background: #fef2f2;
}
.office-modal-item.severity-warning {
border-color: #fde68a;
background: #fffbeb;
}
.office-modal-item.status-error {
border-color: #fca5a5;
background: #fef2f2;
}
.office-modal-empty {
color: #64748b;
font-style: italic;
}
.kpi-modal-sparkline {
width: 100%;
height: 80px;
margin-top: 8px;
background: #10131a;
border-radius: 6px;
}
/* Projekte-Modal (Issue #27: Klick auf den PO -> Projekte des Codeberg-Sync verwalten) */
.office-modal-error {
margin: 0 0 10px;
padding: 8px 12px;
border-radius: 6px;
background: #fef2f2;
border: 1px solid #fca5a5;
color: #991b1b;
font-size: 0.9rem;
}
.repo-config-item {
display: flex;
align-items: center;
gap: 10px;
}
.repo-config-swatch {
flex: none;
width: 16px;
height: 16px;
border-radius: 4px;
border: 1px solid rgba(15, 23, 42, 0.15);
}
.repo-config-slug {
flex: 1;
font-weight: 600;
color: #1f2937;
}
.repo-config-actions {
display: flex;
gap: 4px;
flex: none;
}
.repo-config-form {
flex-wrap: wrap;
}
.repo-config-form-fields {
display: flex;
flex: 1;
gap: 8px;
min-width: 0;
}
.repo-config-form-fields input[type="text"] {
flex: 1;
min-width: 0;
padding: 6px 8px;
border: 1px solid #cbd5e1;
border-radius: 6px;
font-size: 0.9rem;
}
.repo-config-form-fields input[type="color"] {
flex: none;
width: 36px;
height: 32px;
padding: 2px;
border: 1px solid #cbd5e1;
border-radius: 6px;
cursor: pointer;
}
.icon-button {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: 1px solid transparent;
border-radius: 6px;
background: transparent;
color: #475569;
cursor: pointer;
}
.icon-button:hover {
background: #f1f5f9;
border-color: #e2e8f0;
color: #1f2937;
}
.icon-button-svg {
width: 18px;
height: 18px;
}
.repo-config-add-button {
margin-top: 8px;
color: #16a34a;
}
.repo-config-add-button:hover {
background: #f0fdf4;
border-color: #bbf7d0;
color: #15803d;
}
/* Issue #53: Ampel-Symbole (Schreibrechte/Webhook) rechts neben den Bearbeiten-/
Loeschen-Symbolen einer Projektzeile - Tooltip (title) traegt den Pruefgrund. */
.repo-config-status {
display: flex;
gap: 2px;
flex: none;
}
.repo-config-status-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
cursor: help;
}
.repo-config-status-icon.is-ok {
color: #16a34a;
}
.repo-config-status-icon.is-fail {
color: #dc2626;
}
/* Hosts-Modal (Issue #70) - teilt sich Zeilen-, Formular- und Symbolstile mit dem
Projekte-Modal; eigene Regeln braucht nur, was es dort nicht gibt. */
.host-config-subheading {
margin: 18px 0 4px;
font-size: 0.95rem;
color: #1f2937;
}
.host-config-agent {
flex: none;
padding: 2px 8px;
border-radius: 999px;
background: #f1f5f9;
color: #475569;
font-size: 0.8rem;
}