monitor/html/css/office.css
fds2610-bot bd27923321 Reload-Knopf am Kanban-Board: Tickets komplett neu einlesen (Issue #51)
Manchmal werden bei Codeberg geloeschte oder anderweitig verschwundene Issues
vom periodischen Sync nicht bereinigt (er upsertet nur, ruehrt fehlende Issues
nie an). Neuer Knopf neben der "Kanban-Board"-Ueberschrift stoesst dafuer einen
synchronen Voll-Resync an (POST /api/v1/tickets/resync, X-Admin-Key): jedes
konfigurierte Projekt wird neu gelesen, Tickets zu inzwischen verschwundenen
Issues werden je Projekt entfernt (auch wenn sie manuell auf eine nicht vom
Sync verwaltete Spalte wie "fertig" bewegt wurden), Tickets zu weiterhin
existierenden Issues bleiben unangetastet.

Der PO geht dabei sichtbar ans Board und steht dort, bis die Anfrage
abgeschlossen ist - anders als beim automatischen Board-Besuch (feste
Animationsdauer je Stage) macht das die tatsaechliche Sync-Dauer sichtbar.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 19:52:09 +02:00

852 lines
16 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-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);
}
.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-bar {
fill: #334155;
}
.system-tile-led {
fill: #64748b;
}
.status-ok .system-tile-led {
fill: #4ade80;
}
.status-error .system-tile-led {
fill: #f87171;
}
.system-tile-label {
fill: var(--color-text-inverse);
font-size: 12px;
}
.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);
}
.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-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;
}
/* Zahnrad am PO-Platz (Issue #27): zeigt ueberhaupt erst, dass sich hier die
Projektkonfiguration oeffnen laesst. Beim Hover ueber den Platz tritt es hervor. */
.po-config-hint-bg {
fill: #ffffff;
stroke: #94a3b8;
stroke-width: 1.5;
opacity: 0.85;
}
.po-config-hint-tooth,
.po-config-hint-hub {
stroke: #475569;
stroke-width: 2;
stroke-linecap: round;
fill: none;
}
.po-desk:hover .po-config-hint-bg {
fill: #e0f2fe;
stroke: #0284c7;
opacity: 1;
}
.po-desk:hover .po-config-hint-tooth,
.po-desk:hover .po-config-hint-hub {
stroke: #0284c7;
}
/* 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-cup {
fill: #f8fafc;
stroke: #94a3b8;
stroke-width: 1.5;
}
.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-heading {
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;
}