Der Konfig-Dialog liess sich in der Praxis kaum oeffnen (Fehlerbericht in Issue #27). Drei Ursachen, alle reproduziert: - Ein Klick auf den PO selbst tat gar nichts: der Klick-Handler hing nur am Schreibtisch, die Figur liegt aber in der Avatar-Schicht darueber. Genau sie meint das Ticket ("bei Klick auf den PO"). Der Avatar ist jetzt klickbar, auch waehrend er am Board steht. - Klicks auf den Platz trafen nur, wenn sie zufaellig auf einem gefuellten Moebelstueck landeten - eine <g> faengt in ihren Luecken keine Klicks. Der Platz hat jetzt eine eigene Trefferflaeche, wie die Board-Spalten. - Dass hinter dem PO ueberhaupt etwas steckt, war nicht zu sehen. Ein kleines Zahnrad auf seinem Tisch (mit Tooltip) zeigt es an. Ausserdem oeffnet der Dialog jetzt auch bei nicht erreichbarer API - er meldet dann selbst, dass er die Projekte nicht laden konnte, statt tot zu wirken. Und genau dieser Fall trat leicht ein: der eingebaute PHP-Server beantwortet Anfragen nacheinander, eine offene SSE-Verbindung blockiert damit alles andere (gemessen 16s fuer ein GET /api/v1/tickets, ab dem zweiten offenen Monitor dauerhaft). Das Frontend faellt dann auf Demo-Daten zurueck. Mit PHP_CLI_SERVER_WORKERS=4 sind es 0,006s; PRAGMA busy_timeout deckt die Schreibzugriffe ab, die sich jetzt begegnen koennen. ApplicationTest::testFullFlowCreateUpdateAndDeleteRepoConfigWithAdminKey war seit dem Seed in 003_repo_configs.sql rot (legte fds2610/monitor ein zweites Mal an) - der Test nutzt jetzt ein nicht geseedetes Repo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
709 lines
13 KiB
CSS
709 lines
13 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;
|
|
}
|
|
|
|
#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;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.severity-warning {
|
|
fill: #facc15;
|
|
}
|
|
|
|
.severity-error {
|
|
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;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.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);
|
|
}
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.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;
|
|
}
|