Eine gemeinsame flex-wrap-Aktions-Zeile für Schreiben/Übernehmen/Öffnen in
der Ticket-Box; mit Taiga-Knöpfen wird das Fenster auf 31rem breiter
(gemessen an den deutschen Labels), umgebrochen wird nur bei echtem
Platzmangel. Der Öffnen-Knopf heißt nur noch «{ref} öffnen» — «in Taiga»
sagen die Nachbarknöpfe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1751 lines
98 KiB
CSS
1751 lines
98 KiB
CSS
/*! IBM Plex Sans & IBM Plex Mono — Copyright 2019 IBM Corp.
|
||
Licensed under the SIL Open Font License 1.1 — https://scripts.sil.org/OFL
|
||
(kein Reserved Font Name deklariert). Volltext: frontend/src/fonts/OFL.txt */
|
||
/* Lokal eingebettet (self-hosted) statt von Google Fonts geladen — kein
|
||
externer Request, keine IP-Übertragung an Dritte (Datenschutz, D20). Nur die
|
||
genutzten Schnitte (Sans 400/500/600, Mono 400/500), Subsets latin +
|
||
latin-ext (decken DE/EN/ES/FR/PL); andere Skripte fallen ohnehin auf
|
||
system-ui zurück. Der Build inlint die .woff2 als data:-URI (D19). */
|
||
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:400;font-display:swap;
|
||
src:url(./fonts/ibm-plex-sans-latin-400-normal.woff2) format('woff2');
|
||
unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
|
||
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:400;font-display:swap;
|
||
src:url(./fonts/ibm-plex-sans-latin-ext-400-normal.woff2) format('woff2');
|
||
unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
|
||
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:500;font-display:swap;
|
||
src:url(./fonts/ibm-plex-sans-latin-500-normal.woff2) format('woff2');
|
||
unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
|
||
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:500;font-display:swap;
|
||
src:url(./fonts/ibm-plex-sans-latin-ext-500-normal.woff2) format('woff2');
|
||
unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
|
||
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:600;font-display:swap;
|
||
src:url(./fonts/ibm-plex-sans-latin-600-normal.woff2) format('woff2');
|
||
unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
|
||
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:600;font-display:swap;
|
||
src:url(./fonts/ibm-plex-sans-latin-ext-600-normal.woff2) format('woff2');
|
||
unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
|
||
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:400;font-display:swap;
|
||
src:url(./fonts/ibm-plex-mono-latin-400-normal.woff2) format('woff2');
|
||
unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
|
||
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:400;font-display:swap;
|
||
src:url(./fonts/ibm-plex-mono-latin-ext-400-normal.woff2) format('woff2');
|
||
unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
|
||
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:500;font-display:swap;
|
||
src:url(./fonts/ibm-plex-mono-latin-500-normal.woff2) format('woff2');
|
||
unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
|
||
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:500;font-display:swap;
|
||
src:url(./fonts/ibm-plex-mono-latin-ext-500-normal.woff2) format('woff2');
|
||
unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
|
||
|
||
:root{
|
||
--paper:#F3F6F9;
|
||
--grid:rgba(36,52,71,.055);
|
||
--ink:#243447;
|
||
--line:#41556E;
|
||
--card:#FFFFFF;
|
||
--muted:#6B7A8C;
|
||
--or:#0F766E;
|
||
--warn:#B45309;
|
||
--r:8px;
|
||
/* Exakte sichtbare Höhe; JS überschreibt sie auf documentElement aus
|
||
window.visualViewport.height (setViewportHeight). Default = Fallback. */
|
||
--app-height:100dvh;
|
||
}
|
||
*{box-sizing:border-box;margin:0;padding:0}
|
||
body{
|
||
font-family:'IBM Plex Sans',system-ui,sans-serif;
|
||
color:var(--ink);
|
||
background:
|
||
repeating-linear-gradient(0deg,transparent 0 23px,var(--grid) 23px 24px),
|
||
repeating-linear-gradient(90deg,transparent 0 23px,var(--grid) 23px 24px),
|
||
var(--paper);
|
||
/* Mobile Viewport-Höhe: 100vh ist die *große* Höhe (inkl. Fläche hinter der
|
||
unteren Browserleiste) — Footer/Editor-Titelzeile rutschen dahinter. Wo der
|
||
Browser die dvh-Einheit kennt, übersteuert der @supports-Block unten auf
|
||
--app-height (von JS aus window.visualViewport gesetzt, Default 100dvh) —
|
||
das folgt der wirklich sichtbaren Fläche (auch bei Overlay-Leisten wie in
|
||
Brave). Bewusst per @supports getrennt statt gestapelter height-Zeilen,
|
||
damit keine „height wird überschrieben"-Warnung entsteht. */
|
||
height:100vh;
|
||
overflow:hidden;
|
||
display:flex;flex-direction:column;
|
||
padding:24px 20px 12px;
|
||
}
|
||
/* dvh-fähige Browser: exakte sichtbare Höhe statt 100vh (siehe body oben). */
|
||
@supports (height:100dvh){
|
||
body{height:var(--app-height,100dvh)}
|
||
}
|
||
header{max-width:1200px;width:100%;margin:0 auto 16px;flex:0 0 auto}
|
||
.header-row{display:flex;align-items:center;justify-content:space-between;gap:16px}
|
||
.header-tools{display:flex;align-items:center;gap:12px}
|
||
/* Marke + aktives Dokument als Brotkrume (D81): „Werkbaum › name". Die
|
||
Gruppe darf schrumpfen (min-width:0), damit der Chip lange Namen mit
|
||
Ellipse kürzt, statt die Werkzeuge rechts zu verdrängen. */
|
||
.brandgroup{display:flex;align-items:center;gap:10px;min-width:0;flex:1 1 auto}
|
||
.crumb-sep{color:var(--muted);opacity:.6;font-size:1.05rem;flex:0 0 auto;user-select:none}
|
||
.docwrap{position:relative;display:inline-flex;align-items:center;min-width:0}
|
||
/* Dokumentart hinter dem Chip (D90): grau und klein — Auskunft, kein Knopf.
|
||
flex:0 0 auto, damit bei langen Namen der Name die Ellipse bekommt und
|
||
die Art stehen bleibt (sie ist die Information, die vor Verwechslung
|
||
schützt). */
|
||
.dockind{
|
||
flex:0 0 auto;margin-left:8px;color:var(--muted);
|
||
font-size:.72rem;white-space:nowrap;
|
||
}
|
||
/* Verbindungs-Status am Chip (D90-Nachtrag): getrennt/ungesendet in Warnfarbe */
|
||
.dockind.warn{color:var(--warn);font-weight:600}
|
||
/* Der Auslöser sieht jetzt nach Menü aus: gerahmter Chip mit Pfeil — die
|
||
frühere nackte Beschriftung in der Panel-Zeile war als Knopf nicht zu
|
||
erkennen (der Anlass von D81). */
|
||
.doctrigger{
|
||
display:inline-flex;align-items:center;gap:6px;cursor:pointer;min-width:0;
|
||
border:1px solid rgba(36,52,71,.2);border-radius:8px;background:var(--card);
|
||
padding:4px 10px;color:var(--ink);
|
||
font-family:'IBM Plex Mono',monospace;font-size:.78rem;letter-spacing:0;
|
||
}
|
||
.doctrigger:hover,.doctrigger[aria-expanded="true"]{border-color:var(--or);color:var(--or)}
|
||
.doctrigger:focus-visible{outline:2px solid var(--or);outline-offset:2px}
|
||
/* Rückmeldung von „Teilen" (putOnServer, D76/D81): Petrol-Blitz samt Haken
|
||
am Dokumentnamen — er ist das, was jetzt auf dem Server liegt. */
|
||
.doctrigger.done{border-color:var(--or);color:var(--or)}
|
||
.doctrigger.done::before{content:"✓ ";flex:0 0 auto}
|
||
.docname{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600;max-width:min(44vw,520px)}
|
||
.doc-caret{width:13px;height:13px;flex:0 0 auto;opacity:.7}
|
||
/* Das Dokumenten-Menü hängt am Chip im App-Kopf. Es scrollt als GANZES
|
||
(max-height), statt nur in der Liste — auf dem Telefon wurde das alte
|
||
Menü unten abgeschnitten (D81); erwogen waren auf-/zuklappbare Gruppen,
|
||
das Scrollen ist die zustandslose Antwort. Kein `overflow` am Vorfahren
|
||
nötig — der App-Kopf ist keiner (D50 gilt hier nicht). */
|
||
.docmenu{
|
||
position:absolute;top:calc(100% + 6px);left:0;z-index:60;
|
||
width:min(360px,calc(100vw - 24px));max-height:min(72vh,560px);overflow:auto;
|
||
background:var(--card);border:1px solid rgba(36,52,71,.15);border-radius:10px;
|
||
padding:6px;box-shadow:0 8px 24px rgba(36,52,71,.22);
|
||
font-family:'IBM Plex Sans',sans-serif;font-weight:400;letter-spacing:0;
|
||
text-align:left;color:var(--ink);
|
||
}
|
||
.docmenu[hidden]{display:none}
|
||
.docmenu-list{display:flex;flex-direction:column;gap:2px}
|
||
/* Gruppen nach Dokumentart (docKind, D81): Mitgeliefert · Eigene · Quellen. */
|
||
.docsec{
|
||
margin:8px 4px 2px;padding-top:7px;border-top:1px solid rgba(36,52,71,.1);
|
||
font-family:'IBM Plex Mono',monospace;font-size:.62rem;letter-spacing:.1em;
|
||
text-transform:uppercase;color:var(--muted);
|
||
}
|
||
.docmenu-list .docsec:first-child{margin-top:2px;padding-top:0;border-top:0}
|
||
/* Eine Zeile = Wählen-Knopf + Zeilen-Aktionen. Kein Knopf im Knopf
|
||
(ungültiges HTML) — die Zeile ist ein div, beides sind Geschwister. */
|
||
.docitem{display:flex;align-items:center;gap:2px;border-radius:7px}
|
||
.docpick{
|
||
display:flex;align-items:center;gap:8px;flex:1 1 auto;min-width:0;
|
||
border:0;background:transparent;cursor:pointer;text-align:left;
|
||
padding:7px 9px;border-radius:7px;color:var(--ink);
|
||
font-family:inherit;font-size:.86rem;
|
||
}
|
||
.docpick:hover{background:rgba(15,118,110,.08)}
|
||
.docpick[aria-checked="true"]{color:var(--or);font-weight:600}
|
||
.docpick .doccheck{flex:0 0 auto;width:14px;text-align:center;opacity:0}
|
||
.docpick[aria-checked="true"] .doccheck{opacity:1}
|
||
.docitem-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
|
||
.docitem.editing{cursor:default;padding:0 0 0 9px;gap:8px}
|
||
.docrename{
|
||
flex:1 1 auto;min-width:0;
|
||
border:1px solid var(--or);border-radius:6px;
|
||
padding:5px 7px;margin:2px 4px 2px 0;color:var(--ink);background:var(--card);
|
||
font-family:inherit;font-size:.86rem;outline:none;
|
||
}
|
||
/* Zeilen-Aktionen immer sichtbar (Touch kennt kein Hover), aber leise. */
|
||
.docacts{display:inline-flex;gap:2px;flex:0 0 auto;padding-right:4px}
|
||
.dociconbtn{
|
||
display:grid;place-items:center;width:26px;height:26px;padding:0;
|
||
border:0;border-radius:6px;background:transparent;color:var(--muted);cursor:pointer;
|
||
}
|
||
.dociconbtn svg{width:14px;height:14px;display:block}
|
||
.dociconbtn:hover{background:rgba(36,52,71,.08);color:var(--ink)}
|
||
.docdelbtn:hover{background:rgba(216,151,168,.28);color:#B4415E}
|
||
/* Befehle unten: gewöhnliche Menüzeilen statt eines Knopf-Wusts. */
|
||
.docmenu-cmds{
|
||
display:flex;flex-direction:column;gap:2px;margin-top:6px;padding-top:6px;
|
||
border-top:1px solid rgba(36,52,71,.12);
|
||
}
|
||
.doccmd{
|
||
display:flex;align-items:center;gap:8px;width:100%;
|
||
border:0;background:transparent;cursor:pointer;text-align:left;
|
||
padding:7px 9px;border-radius:7px;color:var(--ink);
|
||
font-family:inherit;font-size:.86rem;
|
||
}
|
||
.doccmd:hover{background:rgba(15,118,110,.08)}
|
||
.doccmd svg{width:14px;height:14px;flex:0 0 auto;color:var(--muted)}
|
||
.doccmd-glyph{width:14px;text-align:center;color:var(--muted);font-weight:600;flex:0 0 auto}
|
||
.fsbtn{
|
||
display:inline-flex;align-items:center;justify-content:center;
|
||
width:30px;height:29px;padding:0;cursor:pointer;
|
||
border:1px solid rgba(36,52,71,.2);border-radius:8px;
|
||
background:var(--card);color:var(--muted);
|
||
}
|
||
.fsbtn svg{display:block}
|
||
.fsbtn:hover{border-color:var(--or);color:var(--or)}
|
||
.fsbtn.active{background:var(--or);color:#fff;border-color:var(--or)}
|
||
/* Neuigkeiten (D58). Der Knopf steht permanent in der Kopfzeile; **bernstein**
|
||
heißt „da ist etwas, das du noch nicht gesehen hast" — dieselben Töne, die
|
||
der Strahlenkranz am Knoten trägt (D28), damit Knopf und Knoten dasselbe
|
||
sagen. Petrol (`.active`) heißt etwas anderes: Es wird gerade ein
|
||
bestimmter Tag im Diagramm vorgeführt. */
|
||
.newswrap{position:relative;display:inline-flex;flex:0 0 auto}
|
||
.newsbtn{width:auto;min-width:30px;padding:0 6px;gap:2px}
|
||
.newsbtn.unseen{color:#A16207;border-color:#FACC15;background:#FEFCE8}
|
||
.newsbtn.unseen:hover{color:#854D0E;border-color:#EAB308}
|
||
.newsbtn .freshcount[hidden]{display:none}
|
||
.newsmenu{
|
||
position:absolute;top:calc(100% + 6px);right:0;z-index:60;
|
||
width:min(420px,calc(100vw - 24px));max-height:min(70vh,520px);overflow:auto;
|
||
background:var(--card);border:1px solid rgba(36,52,71,.15);border-radius:10px;
|
||
padding:10px 12px;box-shadow:0 8px 24px rgba(36,52,71,.22);
|
||
text-transform:none;letter-spacing:0;font-family:'IBM Plex Sans',sans-serif;
|
||
text-align:left;color:var(--ink);
|
||
}
|
||
.newsmenu[hidden]{display:none}
|
||
.newshead{
|
||
display:flex;align-items:center;gap:8px;
|
||
font-size:.72rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);
|
||
}
|
||
.newshead .newsclose{
|
||
margin-left:auto;border:0;background:transparent;cursor:pointer;
|
||
color:var(--muted);font-size:1rem;line-height:1;padding:2px 4px;border-radius:6px;
|
||
}
|
||
.newshead .newsclose:hover{color:var(--ink);background:rgba(36,52,71,.07)}
|
||
/* Fußnote zur Sprache der Notizen (D58-Nachtrag): klein, grau, ohne Rahmen
|
||
und ohne Fläche — sie soll gelesen werden können, aber hinter dem Inhalt
|
||
zurückstehen, zu dem sie gehört. */
|
||
.newsnote{margin-top:7px;color:var(--muted);font-size:.73rem;line-height:1.45}
|
||
/* „Seit deinem letzten Besuch" (D28) — die persönliche Hälfte, deshalb
|
||
bernstein abgesetzt und oben, vor der allgemeinen Chronik. */
|
||
.newssince{
|
||
display:flex;align-items:center;gap:8px;flex-wrap:wrap;
|
||
margin-top:8px;padding:8px 10px;border-radius:8px;
|
||
background:#FEFCE8;border:1px solid #FACC15;color:#854D0E;font-size:.82rem;
|
||
}
|
||
.newssince .newsseen{
|
||
margin-left:auto;border:1px solid rgba(133,77,14,.35);border-radius:7px;
|
||
background:transparent;color:inherit;cursor:pointer;
|
||
padding:3px 9px;font-family:inherit;font-size:.78rem;
|
||
}
|
||
.newssince .newsseen:hover{background:rgba(133,77,14,.1)}
|
||
.newsday{margin-top:10px;padding-top:9px;border-top:1px solid rgba(36,52,71,.12)}
|
||
.newsdate{font-weight:600;font-size:.84rem}
|
||
.newslines{margin:5px 0 0;padding-left:17px;font-size:.82rem;line-height:1.55;color:var(--ink)}
|
||
.newslines li{margin:1px 0}
|
||
.newslines code{
|
||
font-family:'IBM Plex Mono',monospace;font-size:.92em;
|
||
background:rgba(36,52,71,.07);border-radius:4px;padding:0 3px;
|
||
}
|
||
.newsshow{
|
||
margin-top:6px;border:1px solid rgba(36,52,71,.2);border-radius:7px;
|
||
background:var(--card);color:var(--muted);cursor:pointer;
|
||
padding:4px 9px;font-family:inherit;font-size:.78rem;
|
||
}
|
||
.newsshow:hover{border-color:var(--or);color:var(--or)}
|
||
.newsshow[aria-pressed="true"]{background:var(--or);color:#fff;border-color:var(--or)}
|
||
.newsempty{margin-top:8px;color:var(--muted);font-size:.82rem}
|
||
/* Vollbild: Panels nutzen fast die ganze Fensterbreite; es bleibt ein
|
||
schmaler Rand (halber Panel-Zwischenraum = 7 px), damit es nicht gedrängt
|
||
wirkt. */
|
||
body.fullscreen{padding-left:7px;padding-right:7px}
|
||
body.fullscreen header,
|
||
body.fullscreen .app,
|
||
body.fullscreen .site-footer{max-width:none}
|
||
h1{font-size:1.25rem;font-weight:600;letter-spacing:-.01em;display:flex;align-items:center;gap:11px}
|
||
/* Build-Hinweis hinter dem Titel (nur nicht-produktive Builds, siehe app.js) */
|
||
.build-badge{margin-left:-.5em;font-size:.58em;line-height:1;vertical-align:middle;cursor:help;user-select:none;text-decoration:none}
|
||
header p{margin-top:4px;color:var(--muted);font-size:.9rem}
|
||
.langsel{
|
||
display:inline-flex;flex:0 0 auto;
|
||
border:1px solid rgba(36,52,71,.2);border-radius:999px;overflow:hidden;
|
||
}
|
||
.langsel button{
|
||
border:0;background:var(--card);cursor:pointer;color:var(--muted);
|
||
font-family:'IBM Plex Mono',monospace;font-size:.72rem;letter-spacing:.04em;
|
||
padding:5px 10px;
|
||
}
|
||
.langsel button + button{border-left:1px solid rgba(36,52,71,.14)}
|
||
.langsel button:hover:not(.active){color:var(--or)}
|
||
.langsel button.active{background:var(--or);color:#fff}
|
||
.langmore{letter-spacing:0;padding:5px 8px;font-weight:600}
|
||
.langsel .lang-extra{display:none} /* PL/RU/HI erst nach Aufklappen */
|
||
.langsel.expanded .lang-extra{display:block}
|
||
.site-footer{
|
||
max-width:1200px;margin:14px auto 0;
|
||
color:var(--muted);font-size:.78rem;text-align:center;
|
||
}
|
||
.site-footer a{color:var(--or);text-decoration:none}
|
||
.site-footer a:hover{text-decoration:underline}
|
||
.site-footer .vibe{cursor:help;text-decoration:underline dotted;text-underline-offset:2px}
|
||
|
||
/* Horizontal (Toggle aus): Diagramm oben, Editor unten — je volle Breite.
|
||
Vertikal (Toggle an, .side): Editor links, Diagramm rechts.
|
||
Zwischen beiden liegt ein verschiebbarer Gutter (Splitter). */
|
||
.app{
|
||
/* Defaults der laufzeit-gesetzten Custom-Properties (JS setzt --col/--drow
|
||
als Inline-Style per Splitter). Werte = die bisherigen var()-Fallbacks;
|
||
die JS-Erkennung „hat der Nutzer gezogen?" liest app.style (Inline) und
|
||
bleibt davon unberührt. (--pmin-d/-e hielten auf Mobil die beiden
|
||
Titelzeilen frei — entfallen mit dem Ein-Bereich-Modell, D17-Nachtrag.) */
|
||
--col:360px;--drow:60%;
|
||
max-width:1200px;width:100%;margin:0 auto;
|
||
flex:1 1 auto;min-height:0;
|
||
display:grid;gap:0;
|
||
grid-template-columns:minmax(0,1fr);
|
||
grid-template-rows:var(--drow,60%) 14px minmax(0,1fr);
|
||
grid-template-areas:"diagram" "gutter" "editor";
|
||
align-items:stretch;
|
||
}
|
||
.app.side{
|
||
grid-template-columns:var(--col,360px) 14px minmax(0,1fr);
|
||
grid-template-rows:minmax(0,1fr);
|
||
grid-template-areas:"editor gutter diagram";
|
||
}
|
||
.panel.editor{grid-area:editor}
|
||
.panel.right{grid-area:diagram}
|
||
.gutter{grid-area:gutter}
|
||
/* Splitter/Gutter: stapelweise horizontal, seitlich vertikal. */
|
||
.gutter{
|
||
position:relative;align-self:stretch;justify-self:stretch;
|
||
height:14px;cursor:row-resize;touch-action:none;
|
||
}
|
||
.app.side .gutter{height:auto;width:14px;cursor:col-resize}
|
||
.gutter::before{
|
||
content:'';position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
|
||
width:46px;height:4px;border-radius:999px;background:rgba(36,52,71,.16);
|
||
transition:background .15s;
|
||
}
|
||
.app.side .gutter::before{width:4px;height:46px}
|
||
.gutter:hover::before,.gutter.dragging::before{background:var(--or)}
|
||
|
||
/* Minimieren: Panel auf seine Titelzeile schrumpfen, Körper ausblenden.
|
||
'a' = Diagramm minimiert (Editor maximal), 'b' = Editor minimiert. */
|
||
.panel.collapsed .editor-body,
|
||
.panel.collapsed .diagram,
|
||
.panel.collapsed .warnings{display:none}
|
||
.panel.collapsed .copybtn,
|
||
.panel.collapsed .switch,
|
||
.panel.collapsed .seg{display:none}
|
||
.panel.collapsed .panel-head{cursor:pointer}
|
||
.panel.collapsed .panel-head>span{cursor:pointer}
|
||
/* Gestapelt: kollabiertes Panel = Titelzeile (auto), anderes füllt. */
|
||
.app:not(.side).collapse-diagram{grid-template-rows:auto 14px minmax(0,1fr)}
|
||
.app:not(.side).collapse-editor{grid-template-rows:minmax(0,1fr) 14px auto}
|
||
/* Seitlich: schmaler vertikaler Streifen mit gedrehter Beschriftung. */
|
||
.app.side.collapse-editor{grid-template-columns:auto 14px minmax(0,1fr)}
|
||
.app.side.collapse-diagram{grid-template-columns:minmax(0,1fr) 14px auto}
|
||
.app.side .panel.collapsed{align-self:stretch}
|
||
.app.side .panel.collapsed .panel-head{
|
||
flex-direction:column;align-items:center;gap:12px;
|
||
height:100%;padding:12px 6px;border-bottom:0;
|
||
}
|
||
.app.side .panel.collapsed .panel-head>span{
|
||
writing-mode:vertical-rl;transform:rotate(180deg);
|
||
margin:0;white-space:nowrap;
|
||
}
|
||
.app.side .panel.collapsed .winbtns{flex-direction:column}
|
||
|
||
/* Schmaler Viewport: Seitenmodus fällt auf gestapelt zurück. */
|
||
@media (max-width:820px){
|
||
.app.side{
|
||
grid-template-columns:minmax(0,1fr);
|
||
grid-template-rows:var(--drow,60%) 14px minmax(0,1fr);
|
||
grid-template-areas:"diagram" "gutter" "editor";
|
||
}
|
||
.app.side.collapse-diagram{grid-template-columns:minmax(0,1fr);grid-template-rows:auto 14px minmax(0,1fr)}
|
||
.app.side.collapse-editor{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr) 14px auto}
|
||
.app.side .panel.collapsed .panel-head{flex-direction:row;align-items:center;height:auto;padding:10px 16px}
|
||
.app.side .panel.collapsed .panel-head>span{writing-mode:horizontal-tb;transform:none}
|
||
.app.side .panel.collapsed .winbtns{flex-direction:row}
|
||
.app.side .gutter{height:14px;width:auto;cursor:row-resize}
|
||
.app.side .gutter::before{width:46px;height:4px}
|
||
}
|
||
|
||
/* Editor-Innenaufbau: Textarea + aufklappbare Agenda.
|
||
Horizontal nebeneinander, vertikal (.side) untereinander. */
|
||
.editor-body{display:flex;align-items:stretch;flex:1 1 auto;min-height:0}
|
||
.editor-body textarea{flex:1 1 auto;min-width:0;min-height:0}
|
||
/* Zeilennummern + Textfeld sind eine Spalte (`.src-wrap`, D33). */
|
||
.src-wrap{display:flex;align-items:stretch;flex:1 1 auto;min-width:0;min-height:0}
|
||
/* Legende: bewusst KEIN <details> mehr. Chrome legt den Details-Inhalt in
|
||
`::details-content`; dadurch war `.hint` kein Flex-Kind von `.agenda` mehr,
|
||
`flex:1/min-height:0` liefen ins Leere und der Inhalt wurde geclippt statt
|
||
scrollbar (D26). Als gewöhnlicher Container ist die Kette wieder dicht. */
|
||
.agenda{
|
||
flex:0 0 auto;
|
||
border-left:1px dashed rgba(36,52,71,.18);
|
||
display:flex;flex-direction:column;min-height:0;min-width:0;
|
||
}
|
||
/* Die Aufteilung steht als feste px-Größe. Schrumpft das Panel später (Zug am
|
||
großen Splitter, Drehung, Tastatur), begrenzt max-* die Legende, damit der
|
||
Editor nicht auf 0 gedrückt wird — gleiche Schranke wie beim Ziehen. */
|
||
.agenda.open{width:var(--hcol,300px);max-width:85%;overflow:hidden}
|
||
.agenda-summary{
|
||
flex:0 0 auto;
|
||
background:none;border:0;text-align:left;
|
||
cursor:pointer;white-space:nowrap;
|
||
padding:10px 16px;
|
||
font-family:'IBM Plex Mono',monospace;
|
||
font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);
|
||
display:flex;align-items:center;gap:7px;
|
||
}
|
||
.agenda-summary::before{content:'▸';display:inline-block;transition:transform .15s}
|
||
.agenda.open>.agenda-summary::before{transform:rotate(90deg)}
|
||
.agenda-summary:focus-visible{outline:2px solid var(--or);outline-offset:-2px}
|
||
.agenda:not(.open) .hint{display:none}
|
||
.agenda .hint{border-top:0;flex:1 1 auto;min-height:0;overflow:auto}
|
||
/* Splitter zwischen Editor und Legende — dieselbe Sprache wie der große
|
||
Splitter, nur schmaler. Ohne offene Legende gibt es nichts zu teilen. */
|
||
/* Neuheiten-Knopf im Diagramm-Kopf (D28): Sternchen + Anzahl, nur sichtbar,
|
||
wenn es etwas gibt. Gelb wie der Strahlenkranz am Knoten. */
|
||
/* Die Zahl der neuen Knoten sitzt seit D58 am Neuigkeiten-Knopf in der
|
||
Kopfzeile (die bernsteinfarbene Fassung steht dort bei `.newsbtn.unseen`). */
|
||
.freshcount{
|
||
font-family:'IBM Plex Mono',monospace;font-size:.62rem;font-weight:600;
|
||
margin-left:3px;vertical-align:2px;
|
||
}
|
||
.hint-gutter{
|
||
flex:0 0 10px;align-self:stretch;
|
||
cursor:col-resize;touch-action:none;
|
||
display:flex;align-items:center;justify-content:center;
|
||
}
|
||
.hint-gutter[hidden]{display:none}
|
||
.hint-gutter::before{content:'';width:3px;height:38px;border-radius:2px;background:rgba(36,52,71,.18)}
|
||
.hint-gutter:hover::before,.hint-gutter.dragging::before{background:var(--or)}
|
||
.side .editor-body{flex-direction:column;align-items:stretch}
|
||
.side .agenda{border-left:0;border-top:1px dashed rgba(36,52,71,.18)}
|
||
.side .agenda.open{width:auto;max-width:none;height:var(--hrow,220px);max-height:85%}
|
||
.side .hint-gutter{cursor:row-resize}
|
||
.side .hint-gutter::before{width:38px;height:3px}
|
||
|
||
.panel{
|
||
background:var(--card);
|
||
border:1px solid rgba(15,118,110,.30); /* dezentes Petrol */
|
||
border-radius:12px;
|
||
overflow:hidden;
|
||
display:flex;flex-direction:column;min-height:0;min-width:0;
|
||
}
|
||
.panel-head{
|
||
flex:0 0 auto;position:relative;
|
||
padding:10px 16px;
|
||
/* gap:10 statt 14 — dieselbe Rechnung wie in D17-Nachtrag 5, nur für den
|
||
Schreibtisch: Beim zehnten Bedienelement (D56) fehlten 10 px, und zehn
|
||
Lücken à 14 px machen 140 px Weißraum für 566 px Knöpfe. Verkleinert
|
||
wird der Zwischenraum, nicht das Ziel; 4 px weniger je Lücke geben die
|
||
Breite des neuen Knopfes zurück. */
|
||
display:flex;justify-content:space-between;align-items:center;gap:10px;
|
||
font-family:'IBM Plex Mono',monospace;
|
||
font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;
|
||
color:var(--muted);
|
||
border-bottom:1px solid rgba(36,52,71,.1);
|
||
flex-wrap:wrap;
|
||
}
|
||
.panel-head>span{margin-right:auto}
|
||
.switch{
|
||
display:flex;align-items:center;gap:7px;cursor:pointer;
|
||
text-transform:none;letter-spacing:0;
|
||
font-family:'IBM Plex Sans',sans-serif;font-size:.78rem;color:var(--muted);
|
||
}
|
||
.switch input{position:absolute;opacity:0;width:0;height:0}
|
||
.switch i{
|
||
width:30px;height:17px;border-radius:999px;background:#C6CFD8;
|
||
position:relative;transition:background .15s;flex:0 0 auto;
|
||
}
|
||
.switch i::before{
|
||
content:'';position:absolute;top:2px;left:2px;width:13px;height:13px;
|
||
border-radius:50%;background:#fff;transition:left .15s;
|
||
box-shadow:0 1px 2px rgba(36,52,71,.25);
|
||
}
|
||
.switch input:checked + i{background:var(--or)}
|
||
.switch input:checked + i::before{left:15px}
|
||
.seg{
|
||
display:inline-flex;align-items:stretch;
|
||
border:1px solid rgba(36,52,71,.2);border-radius:999px;overflow:hidden;
|
||
}
|
||
.seg label{display:flex;cursor:pointer}
|
||
.seg input{position:absolute;opacity:0;width:0;height:0}
|
||
.seg span{
|
||
display:grid;place-items:center;padding:4px 10px;color:var(--muted);
|
||
transition:background .15s,color .15s;
|
||
}
|
||
.seg span svg{display:block}
|
||
.seg label + label span{border-left:1px solid rgba(36,52,71,.14)}
|
||
.seg input:checked + span{background:var(--or);color:#fff}
|
||
.seg input:focus-visible + span{outline:2px solid var(--or);outline-offset:-2px}
|
||
.winbtns{display:inline-flex;gap:4px;align-items:center}
|
||
.winbtn{
|
||
width:19px;height:19px;display:grid;place-items:center;
|
||
border:1px solid rgba(36,52,71,.2);border-radius:5px;
|
||
background:var(--card);color:var(--muted);cursor:pointer;
|
||
font-size:.72rem;line-height:1;padding:0;
|
||
}
|
||
.winbtn:hover{border-color:var(--or);color:var(--or)}
|
||
.winbtn.active{background:var(--or);color:#fff;border-color:var(--or)}
|
||
.copybtn{
|
||
color:var(--muted);background:var(--card);
|
||
border:1px solid rgba(36,52,71,.2);border-radius:6px;
|
||
padding:3px 6px;cursor:pointer;
|
||
display:inline-flex;align-items:center;justify-content:center;
|
||
}
|
||
.copybtn:hover,.copybtn.done{border-color:var(--or);color:var(--or)}
|
||
.copybtn svg{display:block;width:15px;height:15px}
|
||
.copybtn .ic-done{display:none}
|
||
/* `.ic-main` ist die neutrale Form von `.ic-copy` für Knöpfe, die nichts
|
||
kopieren, aber dieselbe Erfolgs-Rückmeldung geben (Stand sichern, D54). */
|
||
.copybtn.done .ic-copy,.copybtn.done .ic-main{display:none}
|
||
.copybtn.done .ic-done{display:block}
|
||
/* Falt-Durchschalter (SPEC §9/D75): vier Icons, sichtbar ist das des
|
||
NÄCHSTEN Schritts — `data-next` setzt updateFoldBtn(). Zwei Klassen, damit
|
||
die Regel `.copybtn svg{display:block}` schlägt. */
|
||
.copybtn.foldbtn .fc-small,
|
||
.copybtn.foldbtn .fc-path,
|
||
.copybtn.foldbtn .fc-closed,
|
||
.copybtn.foldbtn .fc-open{display:none}
|
||
.copybtn.foldbtn[data-next="small"] .fc-small,
|
||
.copybtn.foldbtn[data-next="path"] .fc-path,
|
||
.copybtn.foldbtn[data-next="closed"] .fc-closed,
|
||
.copybtn.foldbtn[data-next="open"] .fc-open{display:block}
|
||
/* Läuft ein Abruf, dreht das Symbol: bei gedrosselter Gegenseite (D31) können
|
||
das die vollen 20 s sein, und ein Knopf ohne Rückmeldung wirkt kaputt. */
|
||
/* Sperre nach einem Abruf (D31): sichtbar gedämpft, aber **klickbar** — der
|
||
Klick meldet den Grund, ein `disabled` erklärte nichts. */
|
||
.padrefresh.cooling{opacity:.45;cursor:not-allowed}
|
||
.padrefresh.busy{color:var(--or);border-color:var(--or);pointer-events:none}
|
||
.padrefresh.busy.cooling{opacity:1;cursor:default}
|
||
.padrefresh.busy svg{animation:padspin 1.1s linear infinite}
|
||
@keyframes padspin{to{transform:rotate(360deg)}}
|
||
@media (prefers-reduced-motion:reduce){
|
||
.padrefresh.busy svg{animation:none}
|
||
.padrefresh.busy{opacity:.55}
|
||
}
|
||
.dlbtn{gap:3px}
|
||
.dl-label{font-family:'IBM Plex Mono',monospace;font-size:.62rem;font-weight:600;letter-spacing:.02em}
|
||
/* Download-Gruppe: Desktop zeigt SVG/PNG inline, der Trigger ist versteckt.
|
||
Auf Mobil (body.mobile, s. u.) klappt sie als Overlay-Menü auf. */
|
||
.dlgroup{display:inline-flex;align-items:center;gap:3px}
|
||
.dlmenu{display:inline-flex;gap:3px}
|
||
.dltrigger{display:none}
|
||
.legendbtn{display:none} /* nur auf kleinem Bildschirm sichtbar */
|
||
.panetoggle{display:none} /* ebenso: dort gibt es nur einen Bereich */
|
||
/* Zurückschreiben der Faltung (D38-Nachtrag 2): Für die Dauer des SYNCHRONEN
|
||
Schreibens braucht das Textfeld einen Kasten — ohne ihn tut `execCommand`
|
||
nichts (gemessen: liefert `false`, obwohl `activeElement` das Feld meldet).
|
||
Auf kleinem Bildschirm ist der Editor `display:none`, wenn das Diagramm
|
||
vorn ist. Absolut aus dem Fluss genommen und aus dem Bild geschoben, damit
|
||
der sichtbare Bereich seine Größe behält; gezeichnet wird davon nichts —
|
||
derselbe Griff wie `exporting` im Grafikexport. */
|
||
body.writing-fold .panel.editor{
|
||
display:flex!important;
|
||
position:absolute!important;left:-10000px!important;top:0!important;
|
||
width:360px!important;height:400px!important;
|
||
}
|
||
/* Stand-Gruppe (D81): Speichern, Kamera, frühere Stände, Neu laden, Teilen —
|
||
die Funktionen des AKTIVEN Dokuments, links neben der Beschriftung. Die
|
||
Gruppe trägt das `margin-right:auto` (schiebt die übrigen Knöpfe nach
|
||
rechts) und ist über `.snapwrap` der Positionsbezug des Snapshot-Menüs.
|
||
Der Dokument-Wähler selbst steht seit D81 als Brotkrume im App-Kopf. */
|
||
.standgroup{display:inline-flex;align-items:center;gap:4px;margin-right:auto}
|
||
/* Die Beschriftung „Text-Editor" bekommt KEIN auto-Margin (die allgemeine
|
||
`.panel-head>span`-Regel gäbe ihr eins) — den Platz nimmt die Stand-Gruppe. */
|
||
.panel-head>span[data-i18n="editorTitle"]{margin-right:0}
|
||
/* Bezug des Menüs ist der KNOPF, nicht die Gruppe: An der Gruppe ausgerichtet
|
||
(`right:0`) begänne es 190 px weiter links und liefe aus dem Panel heraus —
|
||
nachgemessen bei der ersten Fassung. */
|
||
.snapwrap{position:relative;display:inline-flex;flex:0 0 auto}
|
||
/* Snapshot-Menü (D54): am linken Rand seiner Gruppe ausgerichtet — also
|
||
unter dem Knopf. */
|
||
.snapbtn{flex:0 0 auto}
|
||
.snapmenu{
|
||
position:absolute;top:calc(100% + 6px);left:0;z-index:50;min-width:190px;
|
||
max-height:280px;overflow:auto;
|
||
display:flex;flex-direction:column;gap:2px;
|
||
background:var(--card);border:1px solid rgba(36,52,71,.15);border-radius:10px;
|
||
padding:6px;box-shadow:0 8px 24px rgba(36,52,71,.22);
|
||
text-transform:none;letter-spacing:0;font-family:'IBM Plex Sans',sans-serif;
|
||
}
|
||
.snapmenu[hidden]{display:none}
|
||
.snapitem{
|
||
display:flex;align-items:baseline;gap:8px;width:100%;
|
||
border:0;background:transparent;cursor:pointer;text-align:left;
|
||
padding:6px 9px;border-radius:7px;color:var(--ink);
|
||
font-family:inherit;font-size:.84rem;white-space:nowrap;
|
||
}
|
||
.snapitem:hover{background:rgba(15,118,110,.08)}
|
||
.snapitem .snapsize{margin-left:auto;color:var(--muted);font-size:.74rem}
|
||
.snapempty{padding:7px 9px;color:var(--muted);font-size:.8rem}
|
||
.togglebtn[aria-pressed="true"]{background:var(--or);color:#fff;border-color:var(--or)}
|
||
.togglebtn[aria-pressed="true"]:hover{color:#fff}
|
||
/* Kein Zeilenumbruch (D49, `wrap="off"` am Element): Die Einrückung trägt die
|
||
Hierarchie, und ein weicher Umbruch setzt die Fortsetzung an den linken Rand
|
||
— die Ebene wäre damit gerade dort nicht mehr abzulesen, wo eine Zeile lang
|
||
ist. Lange Zeilen bekommen stattdessen einen waagerechten Balken. */
|
||
textarea{
|
||
display:block;width:100%;min-height:0;
|
||
border:0;outline:none;resize:none;overflow:auto;
|
||
padding:14px 16px;
|
||
font-family:'IBM Plex Mono',monospace;
|
||
font-size:.85rem;line-height:1.7;
|
||
color:var(--ink);background:var(--card);
|
||
tab-size:2;
|
||
}
|
||
/* Zeilennummern (D33). Die Warnungen nennen Zeilennummern — ohne Streifen
|
||
muss man sie im Textfeld abzählen. Jede Zahl steht auf einer **gemessenen**
|
||
Höhe (`renderLineNos()` in app.js), nicht auf „Zeilenhöhe × n"; seit D49
|
||
bricht nichts mehr um, aber die Schriftgröße unterscheidet sich zwischen
|
||
Telefon und Schreibtisch, und die Messung stimmt in beiden Fällen von
|
||
selbst. Der Streifen scrollt **nur senkrecht** mit: Er verschiebt sich
|
||
gegen `src.scrollTop`, den waagerechten Balken des Textfelds macht er
|
||
nicht mit — die Zahlen bleiben stehen, wo sie hingehören. Typografie muss
|
||
mit dem Textfeld übereinstimmen, sonst wandern die Zahlen weg. */
|
||
.lineno{
|
||
position:relative;overflow:hidden;flex:0 0 auto;
|
||
background:var(--card);border-right:1px solid rgba(36,52,71,.10);
|
||
font-family:'IBM Plex Mono',monospace;font-size:.85rem;line-height:1.7;
|
||
color:var(--muted);
|
||
-webkit-user-select:none;user-select:none;
|
||
}
|
||
.lineno-inner{position:absolute;top:0;left:0;right:0}
|
||
.lineno-inner span{
|
||
position:absolute;right:6px;
|
||
opacity:.5;font-variant-numeric:tabular-nums;
|
||
}
|
||
/* Cursor-Zeile und Zeilen mit Warnung heben sich ab — genau die beiden Fälle,
|
||
für die man im Streifen überhaupt nachsieht. */
|
||
.lineno-inner span.cur{opacity:1;color:var(--ink);font-weight:600}
|
||
/* Die Zahl einer Warn-Zeile trägt die Meldung als Tooltip (D33-Nachtrag).
|
||
Dafür reicht ihr Kasten über die **ganze** Streifenbreite statt nur über
|
||
die Ziffern: Ein zwei Zeichen breites Ziel trifft man sonst kaum. Die
|
||
Ziffern selbst bleiben, wo sie sind (rechtsbündig, 6 px Abstand). */
|
||
/* Der Zeiger sagt, WAS hier steht: Pfeil mit **Warndreieck** statt des `?`
|
||
von `cursor:help` — dasselbe ⚠ wie vor jeder Meldung im Warnungsbereich
|
||
(D33-Nachtrag 2). Der Pfeil bleibt, sonst zeigte der Zeiger auf nichts;
|
||
die Spitze liegt auf dem Brennpunkt (1 1). `help` ist der Rückfall, wo ein
|
||
Bild-Zeiger nicht angenommen wird. */
|
||
.lineno-inner span.warn{
|
||
opacity:1;color:var(--warn);font-weight:600;
|
||
left:0;right:0;padding-right:6px;text-align:right;
|
||
cursor:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cpath d='M1 1V17.6L5.3 13.5 8.3 20.2 11.2 18.9 8.2 12.4 13.8 12.2Z' fill='%23243447' stroke='%23fff' stroke-width='1.4' stroke-linejoin='round'/%3E%3Cpath d='M20.5 14 29.5 30 11.5 30Z' fill='%23B45309' stroke='%23fff' stroke-width='1.6' stroke-linejoin='round'/%3E%3Cpath d='M19.4 19.8h2.2v5.2h-2.2zM19.4 26.6h2.2v2.2h-2.2z' fill='%23fff'/%3E%3C/svg%3E") 1 1, help;
|
||
}
|
||
.hint{
|
||
padding:12px 16px;border-top:1px dashed rgba(36,52,71,.18);
|
||
font-size:.8rem;color:var(--muted);line-height:1.7;
|
||
}
|
||
/* Bedienung statt Notation — abgesetzt, damit die Legende ihre Rolle behält. */
|
||
.hint-op{margin-top:8px;padding-top:8px;border-top:1px dashed rgba(36,52,71,.18)}
|
||
.hint code{
|
||
font-family:'IBM Plex Mono',monospace;font-size:.75rem;
|
||
background:var(--paper);border:1px solid rgba(36,52,71,.12);
|
||
border-radius:4px;padding:0 5px;
|
||
}
|
||
.hint .or-code{color:var(--or);border-color:rgba(15,118,110,.4)}
|
||
|
||
.diagram{
|
||
flex:1 1 auto;min-height:0;
|
||
padding:28px 16px 24px;
|
||
overflow:auto;
|
||
background:transparent;
|
||
}
|
||
.panel.right{background:rgba(255,255,255,.6);position:relative}
|
||
.warnings{padding:0 16px 14px;font-size:.8rem;color:var(--warn)}
|
||
.warnings div{margin-top:4px}
|
||
/* Personen-Leiste (SPEC §9/D87): je Person eine Pille mit Belastungs-Balken,
|
||
Tipp schaltet die Personen-Linse. Erscheint nur bei Dokumenten mit
|
||
@-Tags; auf dem Telefon scrollt sie waagerecht statt umzubrechen (kein
|
||
Aufklapp-Inhalt darin, die D50-Falle greift also nicht). */
|
||
.peoplebar{
|
||
display:flex;gap:8px;align-items:center;flex-wrap:wrap;
|
||
padding:0 16px 10px;
|
||
}
|
||
.pbperson{
|
||
display:flex;gap:7px;align-items:center;
|
||
border:1px solid rgba(36,52,71,.18);border-radius:999px;
|
||
background:var(--card);cursor:pointer;
|
||
padding:3px 10px 3px 4px;font:inherit;
|
||
}
|
||
.pbperson:hover{border-color:var(--or)}
|
||
.pbperson[aria-pressed="true"]{border-color:var(--or);box-shadow:0 0 0 1.5px var(--or);background:rgba(15,118,110,.06)}
|
||
.pbperson .tag{box-shadow:none}
|
||
/* „ohne Zuständigen": gestrichelte Pille — es ist ein Loch, keine Person. */
|
||
.pbperson .tag.nobody{border-style:dashed;color:var(--muted);background:var(--card)}
|
||
.pbbar{
|
||
width:64px;height:6px;border-radius:999px;overflow:hidden;
|
||
background:rgba(36,52,71,.1);
|
||
}
|
||
.pbfill{display:block;height:100%;background:var(--or);border-radius:999px}
|
||
.pbpct{font-size:.68rem;color:var(--muted);min-width:3ch;text-align:right}
|
||
body.mobile .peoplebar{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;padding:0 10px 8px}
|
||
.panel.collapsed .peoplebar{display:none}
|
||
.empty{color:var(--muted);font-size:.85rem;padding:8px 4px}
|
||
|
||
/* Zoom-Steuerung: schwebt unten rechts über dem Diagramm. */
|
||
.zoomctl{
|
||
position:absolute;right:6px;bottom:6px;z-index:5;
|
||
display:inline-flex;align-items:center;gap:2px;padding:2px;
|
||
background:var(--card);border:1px solid rgba(36,52,71,.18);
|
||
border-radius:8px;box-shadow:0 1px 4px rgba(36,52,71,.14);
|
||
height:28px;
|
||
}
|
||
.zoomctl button{
|
||
border:0;background:transparent;cursor:pointer;color:var(--muted);
|
||
font-family:'IBM Plex Mono',monospace;font-size:.82rem;line-height:1;
|
||
padding:4px 8px;border-radius:6px;min-width:30px;
|
||
}
|
||
#zoomReset{min-width:48px;font-size:.72rem}
|
||
.zoomctl button:hover{background:var(--paper);color:var(--or)}
|
||
.zoomToggle{display:none;padding:4px 8px}
|
||
.zoomToggle svg{width:16px;height:16px}
|
||
.zoomctl.collapsed .zoomToggle{display:block}
|
||
.zoomctl.collapsed .zoomcontrols{display:none}
|
||
.panel.collapsed .zoomctl{display:none}
|
||
|
||
/* ---------- Baum ---------- */
|
||
.tree{width:max-content;margin:0 auto;list-style:none;position:relative}
|
||
.tree ul{list-style:none}
|
||
.tree li{display:flex;flex-direction:column;align-items:center;position:relative}
|
||
.tree > li + li{margin-top:36px}
|
||
|
||
.node{
|
||
position:relative;
|
||
color:var(--ink);
|
||
background:var(--card);
|
||
border:1.5px solid var(--line);
|
||
border-radius:var(--r);
|
||
padding:7px 14px;
|
||
font-size:.88rem;font-weight:500;line-height:1.3;
|
||
/* Lange Labels brechen um, statt den Baum in die Breite zu treiben (D64).
|
||
Die Umbrüche setzt der RENDERER (wrapLabel: ~40 Zeichen je Zeile,
|
||
gleichmäßig verteilt) als `\n` — pre-line macht sie sichtbar, und der
|
||
Kasten schrumpft auf die längste balancierte Zeile. `max-width` +
|
||
`overflow-wrap` sind nur der Rückhalt für ein einzelnes Wort über der
|
||
Grenze. Zentriert (Organigramm-Konvention). Die feste Zeilenhöhe
|
||
bleibt — der 23-px-Abzweig (§9) trifft bei mehrzeiligen Knoten
|
||
deterministisch die Mitte der ERSTEN Zeile. */
|
||
white-space:pre-line;
|
||
max-width:32ch;
|
||
text-align:center;
|
||
overflow-wrap:break-word;
|
||
box-shadow:0 1px 2px rgba(36,52,71,.08);
|
||
}
|
||
a.node{text-decoration:none;cursor:pointer}
|
||
a.node:hover{box-shadow:0 2px 8px rgba(36,52,71,.22)}
|
||
/* Falt-Zeichen (SPEC §9, D38): ▾ offen / „▸ n" eingeklappt, Klickziel fürs
|
||
Umklappen. Gedeckt in Grau — es ist Bedienelement, keine Statusaussage;
|
||
beim Zeigen färbt es sich zur Tinte. */
|
||
/* Als gerahmter Chip statt bloßem Glyph (D64): das nackte ▾ war ~10px klein
|
||
und kaum zu treffen. Die Höhe ist bewusst gedeckelt (feste line-height +
|
||
kein vertikales Padding): Der Chip darf die Zeilenbox des Knotens NICHT
|
||
über die festen 18,3px heben, sonst wandert die 23-px-Abzweig-Geometrie
|
||
(§9) um Halbpixel. */
|
||
.node .fold{
|
||
cursor:pointer;color:var(--line);
|
||
font-size:.75em;margin-right:6px;
|
||
display:inline-block;padding:0 6px;line-height:14px;
|
||
border:1px solid var(--muted);border-radius:5px;
|
||
background:#fff;
|
||
user-select:none;-webkit-user-select:none;
|
||
}
|
||
/* Deckendes Weiß plus Tinte-Glyphe: Die halbtransparente Füllung der ersten
|
||
Fassung verschwand auf den Pastell-Statusfarben („weiß auf hell") — der
|
||
Chip muss sich auf ALLEN acht Füllungen abheben, und Weiß mit sattem Rand
|
||
tut das; auf dem weißen Neutral-Knoten trägt der Rand allein. */
|
||
.node .fold:hover{color:var(--ink);border-color:var(--line);background:#fff}
|
||
/* Der Wurzelknoten bekommt DENSELBEN Chip wie alle anderen — die frühere
|
||
helle Umkehrung (weiße Glyphe auf durchscheinendem Weiß) war für den
|
||
dunklen Tinte-Wurzelknoten gebaut; trägt die Wurzel aber einen Status,
|
||
gewinnt dessen Pastell-Füllung (.node.st-* ist spezifischer, D39), und
|
||
Weiß auf Rosé war praktisch unsichtbar. Der deckend weiße Chip hebt sich
|
||
auch auf der dunklen Wurzel ab (wie die hellen Tag-Pillen dort). */
|
||
/* ”-Marke (SPEC §9, D40): der Knoten trägt eine Beschreibung — im Tooltip
|
||
nachlesbar. Spiegelt das "-Zeichen der Notation; gedeckt, kein Klickziel. */
|
||
.node .desc-mark{color:var(--muted);margin-left:4px;font-weight:600}
|
||
/* Die helle Textfassung gilt nur dem DUNKLEN Wurzelknoten — mit Status ist
|
||
die Wurzel pastellfarben (siehe Falt-Chip oben), und Weiß darauf wäre
|
||
unsichtbar. „Dunkel" heißt: keine Statusklasse. */
|
||
.root-node:not(.st-idee):not(.st-geplant):not(.st-arbeit):not(.st-durchstich):not(.st-fertig):not(.st-prod):not(.st-highrisk):not(.st-verworfen) .desc-mark{color:rgba(255,255,255,.75)}
|
||
/* Knoten-ID vor dem Titel (D56). Zurückgenommen und in der Mono-Schrift des
|
||
Textfelds: Sie ist die Adresse, nicht der Name — der Titel soll die Zeile
|
||
weiter anführen. Kein `margin-right`; das trennende Leerzeichen steht im
|
||
Markup, damit der Grafikexport (er liest den Knotentext) es mitbekommt. */
|
||
.node .nid{
|
||
font-family:'IBM Plex Mono',monospace;font-size:.86em;font-weight:500;
|
||
color:var(--muted);
|
||
}
|
||
/* Nur die dunkle (statuslose) Wurzel — Begründung beim Falt-Chip oben. */
|
||
.root-node:not(.st-idee):not(.st-geplant):not(.st-arbeit):not(.st-durchstich):not(.st-fertig):not(.st-prod):not(.st-highrisk):not(.st-verworfen) .nid{color:rgba(255,255,255,.75)}
|
||
/* Ticket-Referenz im Label (SPEC §11): als Ganzes gehalten — der Browser
|
||
bräche eine zu breit geratene Zeile sonst am Bindestrich mitten im Token
|
||
(`#US-`/`123`). Die Spanne setzt render.js (markTicketRefs). */
|
||
.node .tref{white-space:nowrap}
|
||
/* Abweichungs-Marke (D91-Nachtrag 10): Das Ticket hinter dieser Ref sagt
|
||
etwas anderes als die Statusbox — die Ref selbst wird warnfarben, kein
|
||
neues Element in den ohnehin dichten Knoten-Ecken. Nur Anzeige aus der
|
||
Sitzung: nicht im Grafikexport (der liest die Knotenfarbe, nicht die
|
||
Spanne) und nicht im Druck. */
|
||
.node .tref-diff{color:var(--warn);font-weight:600}
|
||
/* Das Badge für Refs, die (nur) die Knoten-ID sind — dort steht die Ref
|
||
sonst nirgends im Knoten. Klein und nachgestellt wie die ”-Marke. */
|
||
.node .tref-badge{margin-left:4px;font-size:.86em}
|
||
/* Der Knopf trägt das Zeichen selbst statt eines Icons — in der Größe der
|
||
SVGs daneben, damit die Kopfzeile ihre Höhe behält. */
|
||
.copybtn.idsbtn span{
|
||
display:block;width:15px;height:15px;line-height:15px;text-align:center;
|
||
font-family:'IBM Plex Mono',monospace;font-size:14px;font-weight:600;
|
||
}
|
||
/* Sichtbarer Tastatur-Fokus (Knoten sind fokussierbar: Fokusreihenfolge =
|
||
Lese-/Dokumentreihenfolge; Screenreader liest den aria-label). */
|
||
.node:focus-visible{outline:2px solid var(--or);outline-offset:2px;box-shadow:0 2px 8px rgba(36,52,71,.22)}
|
||
/* Knoten der Cursor-Zeile im Editor (D25). Weißer Halo + Tinte-Ring: hebt sich
|
||
von allen Pastell-Status UND vom dunklen Wurzelknoten ab und ist vom
|
||
petrolfarbenen Fokusring unterscheidbar. Rein visuelle Editierhilfe — nicht
|
||
im Druck. Der `#out`-Präfix ist nötig: `ul.or .node{box-shadow:none}`
|
||
(weiter unten) ist spezifischer als `.node.current` und schluckte den Ring
|
||
sonst überall unterhalb einer any-of-Gruppe. */
|
||
/* Der Ring allein war zu leise (D25-Nachtrag): Er benutzt denselben Kanal, den
|
||
schon jeder Knoten belegt — **jeder Status hat einen Rahmen** (SPEC §4). Ein
|
||
weiterer Ring in einem Feld aus lauter gerahmten Kästchen ist ein Unterschied
|
||
im Grad, nicht in der Art. Deshalb kommt **Tiefe** dazu: Schlagschatten plus
|
||
leichte Vergrößerung. Das ist der einzige Kanal, den im Diagramm noch nichts
|
||
belegt (nichts anderes wirft Schatten oder hebt sich), kollidiert also mit
|
||
keiner Farbcodierung und wirkt über allen acht Statusfarben gleich.
|
||
`transform` ändert **kein Layout** — die Linien bleiben stehen; und weil um
|
||
die **Mitte** skaliert wird, bleibt auch die Knotenmitte unverändert, auf die
|
||
`alignStems()` und `drawCheapPath()` messen. `z-index`, weil Schatten und
|
||
vergrößerter Knoten AUSSERHALB der Box liegen (dasselbe wie bei `.fresh`). */
|
||
#out .node.current{
|
||
box-shadow:0 0 0 2px var(--card),0 0 0 4px var(--ink),0 10px 20px -6px rgba(36,52,71,.5);
|
||
transform:scale(1.04);z-index:3;
|
||
}
|
||
/* Fokusmarke `!!!` (SPEC §1, D32): **Strahlenkranz in Petrol**, nach dem Muster
|
||
der Neuheiten-Markierung (D28), nur in der Akzentfarbe der App statt in Gelb.
|
||
Der geteilte Ring mit der Cursor-Zeile war zu leise — „das erkennt man zu
|
||
wenig" (Nutzer) —, und ein Ring allein wächst beim Zoomen nicht mit dem
|
||
Blickbedürfnis. Der Kranz löst zugleich die in D32 hingenommene Schwäche:
|
||
Cursor-Zeile (Tinte, innen) und fremde Marke (Petrol, außen) sind jetzt
|
||
unterscheidbar. Kein Blinken (WCAG 2.2.2/2.3.1, vgl. D28), und `z-index`,
|
||
weil der Schein AUSSERHALB der Knotenbox liegt — ohne ihn malt jedes später
|
||
kommende Geschwister seinen Hintergrund darüber und schneidet ihn ab. */
|
||
/* Der Schein ist **helles** Teal (#14B8A6), nicht das dunkle Petrol des Rings:
|
||
Ein dunkler Schein auf weißem Grund liest sich als Schatten, ein heller als
|
||
Licht — deshalb wirkte die erste Fassung leiser als der gelbe Kranz aus D28,
|
||
obwohl die Geometrie dieselbe war. Der Ring bleibt dunkles Petrol und ordnet
|
||
die Marke der Akzentfarbe zu; das Leuchten macht die Reichweite. */
|
||
#out .node.focusmark{box-shadow:0 0 0 3px var(--or),0 0 20px 7px rgba(20,184,166,.85);z-index:2}
|
||
/* Beides zugleich: Cursor-Zeile innen (Tinte), fremde Marke außen (Petrol).
|
||
Die Erhebung der Cursor-Zeile gilt in jeder Kombination — der Schlagschatten
|
||
wird deshalb hier mit angehängt (das `transform` erbt die Regel oben). */
|
||
#out .node.focusmark.current{box-shadow:0 0 0 2px var(--card),0 0 0 4px var(--ink),0 0 20px 7px rgba(20,184,166,.85),0 10px 20px -6px rgba(36,52,71,.5);z-index:3}
|
||
/* Neu in Produktion seit dem letzten Besuch (D28): gelber Strahlenkranz um den
|
||
Knoten. Bewusst ein Schein nach AUSSEN statt Farbe im Knoten — die Füllung
|
||
gehört dem Status (SPEC §4) und bleibt lesbar. Kein Blinken: WCAG 2.2.2/2.3.1
|
||
und es zöge dauerhaft den Blick. `#out`-Präfix wegen `ul.or .node`. */
|
||
/* `z-index`, weil der Schein AUSSERHALB des Knotens liegt: ohne ihn malt jedes
|
||
später kommende Geschwister seinen undurchsichtigen Hintergrund darüber und
|
||
schneidet den Kranz einseitig ab (in engen Layouts bleibt nur eine gelbe
|
||
Kante stehen — sieht aus wie ein halber Hintergrund statt eines Rahmens). */
|
||
#out .node.fresh{box-shadow:0 0 0 2px #FACC15,0 0 16px 5px rgba(234,179,8,.65);z-index:2}
|
||
/* Beides zugleich: Cursor-Zeile innen (Tinte), Neuheit außen (gelb). */
|
||
#out .node.fresh.current{box-shadow:0 0 0 2px var(--card),0 0 0 4px var(--ink),0 0 18px 6px rgba(234,179,8,.65),0 10px 20px -6px rgba(36,52,71,.5);z-index:3}
|
||
/* Neu in Produktion UND jemand zeigt darauf: Ring in Petrol (der bewusste
|
||
Zuruf, innen), Kranz in Gelb (die stehende Nachricht, außen). Zwei Scheine
|
||
nach außen gingen nicht — einer muss der Ring sein. Steht bewusst **nach**
|
||
`.node.focusmark.current`: gleiche Spezifität (drei Klassen), und wenn alles
|
||
drei zutrifft, soll Zuruf + Neuheit gewinnen, nicht die eigene Cursor-Zeile. */
|
||
#out .node.fresh.focusmark{box-shadow:0 0 0 3px var(--or),0 0 20px 7px rgba(234,179,8,.65);z-index:2}
|
||
/* Puls beim Zeilenwechsel (D25-Nachtrag): Man verliert den Knoten beim
|
||
**Bewegen**, nicht im Stillstand — also meldet er sich genau dann einmal.
|
||
Einmalig, kein Blinken: WCAG 2.2.2 zielt auf Blinkendes, 2.3.1 auf Flackern
|
||
über 3 Hz; ein einzelner Durchlauf ist keins von beidem (vgl. D28).
|
||
Ausgelöst wird er in `highlightCurrentNode()` **nur** bei geändertem
|
||
`caretLine` — sonst pulste er bei jedem Tastendruck.
|
||
Bewusst NICHT über `box-shadow` animiert: die Kombinationen mit `.fresh` und
|
||
`.focusmark` haben je eigene Schatten-Listen, eine Animation darauf ließe den
|
||
gelben bzw. petrolfarbenen Kranz für die Dauer des Pulses verschwinden.
|
||
Stattdessen zwei Kanäle, die niemand sonst benutzt: ein kurzer Hüpfer im
|
||
`transform` und ein auslaufender Ring auf dem freien `::after`
|
||
(`::before` gehört dem Optional-Kreis, D29). */
|
||
@keyframes nodeLift{0%{transform:scale(1.04)}40%{transform:scale(1.11)}100%{transform:scale(1.04)}}
|
||
@keyframes nodeRipple{from{transform:scale(1);opacity:.5}to{transform:scale(1.4);opacity:0}}
|
||
#out .node.current.pulse{animation:nodeLift .36s ease-out 1}
|
||
/* `both` ist Pflicht: ohne Fill-Mode schnappt der Ring am Ende auf die
|
||
Anfangsdeckkraft zurück und bliebe stehen. */
|
||
#out .node.current.pulse::after{
|
||
content:'';position:absolute;inset:-3px;border-radius:calc(var(--r) + 3px);
|
||
border:2px solid var(--ink);pointer-events:none;
|
||
animation:nodeRipple .45s ease-out 1 both;
|
||
}
|
||
@media (prefers-reduced-motion: reduce){
|
||
#out .node.current.pulse{animation:none}
|
||
#out .node.current.pulse::after{display:none}
|
||
}
|
||
/* Während `diagramToSvg()` misst: Erhebung und Puls abschalten — der Export
|
||
zeichnet aus der Live-Geometrie nach, und die Vergrößerung ist eine
|
||
Editierhilfe, keine Aussage über den Plan (D25). */
|
||
#out.exporting .node.current{transform:none;animation:none}
|
||
#out.exporting .node.current::after{display:none}
|
||
/* Alt gedrückt = Sprung-Geste scharf: Cursor an allen Knoten, Petrol-Ring am
|
||
Knoten unter dem Zeiger. Auf verlinkten Knoten ist das die einzige
|
||
Rückmeldung, weil dort der einfache Klick dem Link gehört (D25). */
|
||
#out.alt .node{cursor:alias}
|
||
#out.alt .node:hover{box-shadow:0 0 0 2px var(--card),0 0 0 4px var(--or)}
|
||
/* Langer Druck „scharf": derselbe Petrol-Ring wie unter dem Alt-Zeiger. */
|
||
#out .node.armed{box-shadow:0 0 0 2px var(--card),0 0 0 4px var(--or)}
|
||
/* Auf Touch ist der lange Druck die Sprung-Geste — die native Textauswahl und
|
||
das Link-Callout des Browsers starten zur selben Zeit und rissen sonst die
|
||
Auswahl im Textfeld wieder an sich (D25). */
|
||
@media (hover:none) and (pointer:coarse){
|
||
.node{-webkit-touch-callout:none;-webkit-user-select:none;user-select:none}
|
||
}
|
||
.ext{font-size:.72em;margin-left:5px;opacity:.55}
|
||
.size{
|
||
position:absolute;top:-9px;right:-9px;
|
||
font-family:'IBM Plex Mono',monospace;
|
||
font-size:.58rem;font-weight:500;letter-spacing:.02em;
|
||
background:var(--or);color:#fff;
|
||
border:1.5px solid var(--card);
|
||
border-radius:999px;padding:1px 6px;line-height:1.4;
|
||
}
|
||
.risk{
|
||
position:absolute;top:-10px;left:-10px;
|
||
width:18px;height:18px;
|
||
display:flex;align-items:center;justify-content:center;
|
||
font-size:.62rem;line-height:1;
|
||
background:var(--card);color:#F97316;
|
||
border:1.5px solid #F97316;border-radius:999px;
|
||
box-shadow:0 0 0 1.5px var(--card);
|
||
}
|
||
.tags{
|
||
position:absolute;right:-9px;bottom:-9px;
|
||
display:flex;gap:3px;
|
||
}
|
||
/* Diskrepanz-Marke des effektiven Status (SPEC §9, D39): die eigene
|
||
Statusbox in den eigenen §4-Farben (nutzt die .chip-Farbregeln der
|
||
Legende), unten links — die letzte freie Knoten-Ecke. */
|
||
.node .chip.ownst{
|
||
position:absolute;left:-9px;bottom:-9px;
|
||
font-size:.58rem;font-weight:500;letter-spacing:.02em;
|
||
padding:1px 5px;line-height:1.4;border-radius:999px;
|
||
}
|
||
.tag{
|
||
font-family:'IBM Plex Mono',monospace;
|
||
font-size:.58rem;font-weight:500;letter-spacing:.02em;
|
||
background:#EEF1F4;color:var(--ink);
|
||
border:1.5px solid var(--line);
|
||
border-radius:999px;padding:1px 6px;line-height:1.4;
|
||
box-shadow:0 0 0 1.5px var(--card);
|
||
}
|
||
/* Zuständigen-Engpass (SPEC §9/D71): die Pille der Person, die mehr als die
|
||
Hälfte der offenen Pfad-Arbeit trägt — Warnfarbe wie das
|
||
Größen-Konflikt-Badge (D62); der Export liest die gemessenen Farben. */
|
||
.tag.overload{background:var(--warn);color:#fff;border-color:var(--warn)}
|
||
/* Personen-Linse (D87): die Pille der gewählten Person in Petrol — welche
|
||
der sichtbaren Knoten ihr gehören. Die Engpass-Warnfarbe gewinnt (der
|
||
Renderer vergibt nur eine der beiden Klassen). */
|
||
.tag.lens{background:var(--or);color:#fff;border-color:var(--or)}
|
||
.ghost-node{
|
||
position:relative;margin-top:14px;
|
||
border:1.5px dashed var(--warn);
|
||
color:var(--warn);background:rgba(180,83,9,.06);
|
||
border-radius:var(--r);padding:4px 12px;
|
||
font-size:.74rem;font-style:italic;white-space:nowrap;
|
||
}
|
||
.ghost-node::before{
|
||
content:'';position:absolute;top:-14px;left:50%;height:14px;
|
||
border-left:2px dashed var(--warn);
|
||
}
|
||
.root-node{background:var(--ink);border-color:var(--ink);color:#fff;font-weight:600;padding:9px 20px}
|
||
|
||
/* Günstigster Pfad (Inversion): nicht benötigte Knoten (nicht-gewählte
|
||
any-of-Alternativen samt Teilbaum) treten zurück (blass, entsättigt);
|
||
der günstige Pfad hebt sich dadurch von allein ab. */
|
||
.cheap-on .node:not(.cheap){opacity:.32;filter:saturate(.4)}
|
||
/* „Neu in Produktion" darf die Pfad-Inversion nicht wegdimmen: opacity und
|
||
saturate treffen sonst auch den gelben Kranz (0,32 Deckkraft ⇒ praktisch
|
||
unsichtbar), und gerade bei einer nicht gewählten Alternative ist „das ist
|
||
jetzt live" die interessantere Nachricht (D28). */
|
||
.cheap-on .node:not(.cheap).fresh{opacity:1;filter:none}
|
||
/* Dasselbe für die Fokusmarke `!!!` (SPEC §1), und hier noch zwingender: Ein
|
||
Zeigefinger, der auf einen ausgeblassten Knoten zeigt, zeigt auf nichts —
|
||
und „sollten wir diese verworfene Alternative noch einmal ansehen?" ist einer
|
||
der wahrscheinlichsten Gründe, überhaupt zu zeigen. Anders als beim
|
||
optionalen Knoten (D29), wo das Zurücktreten die Aussage IST. */
|
||
.cheap-on .node:not(.cheap).focusmark{opacity:1;filter:none}
|
||
/* Die Cursor-Zeile auf einem zurückgetretenen Knoten (D25-Nachtrag 4): Nur
|
||
der RING wird hervorgehoben, die Füllung bleibt blass. Die erste Fassung
|
||
stellte den ganzen Knoten auf volle Stärke — dann verlor er beim bloßen
|
||
Hineinsetzen des Cursors seine Pfad-Aussage („nicht nötig"). Ganz gedimmt
|
||
lassen geht aber auch nicht: opacity/filter am Element treffen den
|
||
box-shadow mit, der Ring wäre unsichtbar (der ursprüngliche D25-Befund).
|
||
Deshalb bleibt der Knoten voll deckend, und die Blässe wird NACHGEBILDET:
|
||
- background-image legt einen Papier-Wasch (68 %) ÜBER die Statusfarbe —
|
||
rechnerisch dasselbe wie 0,32 Deckkraft über dem hellen Grund;
|
||
- Text- und Rahmenfarbe wechseln auf das gedimmte Grau (der Unterschied
|
||
der acht Status-Rahmenfarben ist bei 0,32 ohnehin nicht mehr zu sehen);
|
||
- die Kinder (Badges, Pillen, Chips) dimmen einzeln per opacity — genau
|
||
so, wie es vorher die Element-Deckkraft tat. `.ext` ist ausgenommen:
|
||
es erbt die schon gefadete Textfarbe und wäre sonst doppelt gedimmt.
|
||
fresh/focusmark/done sind ausgeschlossen — deren Ausnahmen (volle Stärke)
|
||
haben je eigene Gründe und gelten weiter. Der Puls-Ring (::after) und der
|
||
Schlagschatten bleiben unberührt in voller Stärke. */
|
||
.cheap-on .node:not(.cheap):not(.fresh):not(.focusmark):not(.done).current{
|
||
opacity:1;filter:none;
|
||
background-image:linear-gradient(rgba(243,246,249,.68),rgba(243,246,249,.68));
|
||
border-color:rgba(107,122,140,.4);
|
||
color:rgba(36,52,71,.35);
|
||
}
|
||
.cheap-on .node:not(.cheap):not(.fresh):not(.focusmark):not(.done).current > :not(.ext){
|
||
opacity:.32;filter:saturate(.4);
|
||
}
|
||
/* Erledigtes tritt NIE zurück (D46-Nachtrag). D46 hatte das schon entschieden
|
||
— „sie auszublassen hieße, sie sähen aus wie eine verworfene Alternative" —,
|
||
die Regel griff aber nur für erledigte Knoten AUF dem Pfad. Ein fertiger
|
||
OPTIONALER Knoten (`+`, D29: nie auf dem Pfad) und eine fertige, nicht
|
||
gewählte Alternative fielen durch das Raster: `#ed.fresh` ist gebaut und
|
||
deployed, sah aber aus wie etwas, das nie passieren wird. Blassheit heißt
|
||
jetzt einheitlich „hier ist nichts getan und wird auch nichts getan". */
|
||
.cheap-on .node:not(.cheap).done{opacity:1;filter:none}
|
||
/* implizit als M angenommene Größe: invertiertes Badge (weiß statt petrol) */
|
||
.size.implicit{background:var(--card);color:var(--or);border-color:var(--or)}
|
||
/* Größen-Konflikt (SPEC §5/D62): die Teilpakete übersteigen zusammen die
|
||
angegebene Größe — das Badge wechselt auf die Warnfarbe, der weiße
|
||
Trennrand zur Knotenkante bleibt. */
|
||
.size.conflict{background:var(--warn)}
|
||
/* Günstigster-Pfad-Linie: gestrichelt, geschwungen, petrol — fädelt durch
|
||
die Endknoten (Blätter) des Pfads. Zwei Overlays in #out (erben dessen
|
||
zoom): die kräftige Linie liegt HINTER den Knoten (nur in den Lücken
|
||
sichtbar), eine abgetönte Kopie liegt DAVOR (deutet den Verlauf beim
|
||
Durchschreiten von Knoten nur schwach an). Kleine Stationspunkte an den
|
||
Blättern zeigen, welche Knoten wirklich auf dem Pfad enden — durchquerte
|
||
Fremdknoten tragen keinen Punkt. Reihenfolge im DOM steuert vorn/hinten. */
|
||
.cheap-overlay{position:absolute;left:0;top:0;overflow:visible;pointer-events:none}
|
||
/* Die VORDERE Ebene muss über JEDEN Knoten — auch über die, die sich selbst
|
||
eine Stapelposition nehmen: Cursor-Zeile (`z-index:3`, D25),
|
||
Strahlenkränze (`z-index:2`, D28/D32). Ohne das deckt ausgerechnet der
|
||
hervorgehobene Knoten seinen eigenen Stationspunkt zu. „Vorn" heißt vorn.
|
||
(Die Abhängigkeits-Kanten hatten aus demselben Grund einmal `.dep-front`
|
||
mit `z-index:4`; sie liegen jetzt vollständig hinten — D41-Nachtrag 2.) */
|
||
.cheap-overlay.cheap-front{z-index:5}
|
||
/* Querverbindungen der Abhängigkeiten (SPEC §9, D41): blassgrau GEPUNKTET
|
||
und gekrümmt — Punktierung und Krümmung unterscheiden sie doppelt von den
|
||
Baumlinien (durchgezogen/gestrichelt, orthogonal) und halten sie leise
|
||
(D41-Nachtrag). Hervorgehobene Kanten (fokussierter Knoten/Cursor-Zeile)
|
||
durchgezogen in Tinte. */
|
||
.dep-overlay{position:absolute;left:0;top:0;overflow:visible;pointer-events:none}
|
||
.dep-edge{fill:none;stroke:var(--muted);stroke-width:1.5;opacity:.4;
|
||
stroke-linecap:round;stroke-dasharray:.1 3}
|
||
/* Offene Pfeilspitze — grau und dünn wie die Linie, kein gefülltes Dreieck. */
|
||
.dep-arrow{fill:none;stroke:var(--muted);stroke-width:1.5;opacity:.4;
|
||
stroke-linecap:round;stroke-linejoin:round}
|
||
.dep-edge.hl{stroke:var(--line);stroke-width:2;opacity:.9;stroke-dasharray:none}
|
||
.dep-arrow.hl{stroke:var(--line);stroke-width:2;opacity:.9}
|
||
.cheap-path{fill:none;stroke:var(--or);stroke-width:2.5;stroke-linecap:round;
|
||
stroke-dasharray:8 6;opacity:.9}
|
||
.cheap-path.faint{opacity:.2}
|
||
/* Stationspunkt: groß, aber blass gefüllt — der Knotentext bleibt lesbar */
|
||
.cheap-dot{fill:var(--or);fill-opacity:.2;stroke:var(--or);stroke-opacity:.35;stroke-width:1.5}
|
||
|
||
/* ---------- Knoten-Fenster für Touch (SPEC §6/§9, D52) ----------
|
||
Auf einem Zeigergerät zeigt der `title` den Tooltip; ohne Zeiger gibt es
|
||
keinen. Der einfache Tipp öffnet deshalb dieses Fenster mit demselben
|
||
Inhalt — und mit dem, was ein `title` nicht kann: Absatz, echte Trennlinie,
|
||
Knöpfe. `position:fixed` (JS setzt left/top aus `getBoundingClientRect()`,
|
||
die den `zoom` von #out schon enthält): Ein Kind von #out erbte dessen
|
||
`zoom` und würde von dessen `overflow` beschnitten (D50). */
|
||
.nodetip{
|
||
position:fixed;z-index:60;max-width:min(21rem, calc(100vw - 24px));
|
||
background:var(--card);color:var(--ink);
|
||
border:1px solid rgba(36,52,71,.15);border-radius:10px;
|
||
box-shadow:0 8px 24px rgba(36,52,71,.3);
|
||
padding:10px 30px 10px 12px;font-size:.78rem;line-height:1.45;
|
||
}
|
||
/* Mit Taiga-Knöpfen darf das Fenster breiter werden, damit die Aktionen
|
||
nebeneinander stehen (D91-Nachtrag 11); die 21rem bleiben das Maß für
|
||
reine Text-Fenster. */
|
||
.nodetip.tipwide{max-width:min(31rem, calc(100vw - 24px))}
|
||
.nodetip[hidden]{display:none}
|
||
.nodetip-x{
|
||
position:absolute;top:2px;right:2px;width:26px;height:26px;
|
||
display:flex;align-items:center;justify-content:center;
|
||
background:none;border:0;border-radius:6px;cursor:pointer;
|
||
color:var(--muted);font-size:1.05rem;line-height:1;
|
||
}
|
||
.nodetip-x:hover{color:var(--ink);background:rgba(36,52,71,.06)}
|
||
/* Absätze kommen aus Leerzeilen der Notation (§1); JS baut sie zu <p>. */
|
||
.nodetip-desc p + p{margin-top:.55em}
|
||
/* Die echte Linie an der Stelle, an der der Tooltip 24 `─` zeichnen musste. */
|
||
.nodetip-facts{
|
||
margin-top:8px;padding-top:7px;border-top:1px solid rgba(36,52,71,.15);
|
||
color:var(--muted);font-size:.72rem;
|
||
}
|
||
/* Ohne Beschreibung gibt es nichts zu trennen — der Strich stünde sonst als
|
||
erste Zeile im Fenster. Dieselbe Regel wie im Tooltip (D40-Nachtrag). */
|
||
.nodetip-facts:first-child{margin-top:0;padding-top:0;border-top:0}
|
||
.nodetip-link{
|
||
display:inline-flex;align-items:center;gap:5px;margin-top:9px;
|
||
padding:5px 10px;border:1px solid var(--or);border-radius:7px;
|
||
color:var(--or);text-decoration:none;font-size:.74rem;font-weight:500;
|
||
}
|
||
/* Taiga-Aktionen im Knoten-Fenster (D91) — dasselbe Knopf-Idiom wie der
|
||
↗-Link, nur als <button>. */
|
||
.nodetip-taiga{display:flex;flex-wrap:wrap;gap:6px;margin-top:9px}
|
||
.nodetip-taigabtn{
|
||
padding:5px 10px;border:1px solid var(--or);border-radius:7px;
|
||
background:none;color:var(--or);cursor:pointer;
|
||
font:inherit;font-size:.74rem;font-weight:500;
|
||
}
|
||
.nodetip-taigabtn:hover{background:rgba(15,118,110,.08)}
|
||
/* Der gelesene Ticket-Stand (D91-Nachtrag 6): abgesetzt wie die Kurz-Fakten,
|
||
die Statusbox als gewöhnlicher .chip in den §4-Farben. */
|
||
.nodetip-ticket{
|
||
margin-top:9px;padding-top:8px;
|
||
border-top:1px solid rgba(36,52,71,.15);font-size:.74rem;
|
||
}
|
||
.nodetip-ticket .tk-head{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
|
||
.nodetip-ticket .tk-status{font-weight:500}
|
||
.nodetip-ticket .tk-arrow{color:var(--muted)}
|
||
.nodetip-ticket .tk-line{margin-top:4px;color:var(--muted)}
|
||
.nodetip-ticket .tk-err{margin-top:2px;margin-bottom:6px;color:var(--warn)}
|
||
.nodetip-ticket .tk-mini{margin-left:auto;padding:2px 7px;font-size:.8rem;line-height:1.1}
|
||
/* Abweichung zwischen Ticket und Statusbox (D91-Nachtrag 7): markiert in der
|
||
Warnfarbe, beide Richtungen als ausdrückliche Knöpfe darunter. */
|
||
.nodetip-ticket .tk-diff{
|
||
display:flex;align-items:center;gap:6px;flex-wrap:wrap;
|
||
margin-top:6px;color:var(--warn);font-weight:500;
|
||
}
|
||
.nodetip-ticket .tk-act{display:flex;flex-wrap:wrap;gap:6px;margin-top:7px}
|
||
/* Die Spitze zeigt auf den Knoten — ohne sie wäre bei dicht stehenden Knoten
|
||
nicht zu sehen, welcher gemeint ist. Ein gedrehtes Quadrat mit zwei Kanten:
|
||
so erbt es Rahmen UND Schatten des Fensters. JS setzt --tipx (Spitze) und
|
||
dreht sie per .above nach unten, wenn das Fenster über dem Knoten sitzt. */
|
||
.nodetip-arrow{
|
||
position:absolute;top:-6px;left:var(--tipx, 50%);width:11px;height:11px;
|
||
margin-left:-5.5px;background:var(--card);
|
||
border-left:1px solid rgba(36,52,71,.15);
|
||
border-top:1px solid rgba(36,52,71,.15);
|
||
transform:rotate(45deg);
|
||
}
|
||
.nodetip.above .nodetip-arrow{
|
||
top:auto;bottom:-6px;
|
||
border:0;border-right:1px solid rgba(36,52,71,.15);
|
||
border-bottom:1px solid rgba(36,52,71,.15);
|
||
}
|
||
/* Solange das Fenster offen ist, trägt sein Knoten einen Petrol-Ring — wie
|
||
der „scharfe" Knoten beim langen Druck (D25). #out-Präfix nötig, weil
|
||
`ul.or .node{box-shadow:none}` spezifischer ist. */
|
||
#out .node.tipped{box-shadow:0 0 0 3px rgba(15,118,110,.55)}
|
||
|
||
/* ---------- ID-Vorschläge beim Tippen von Abhängigkeiten (D63) ----------
|
||
`position:fixed` auf <body> wie das Knoten-Fenster: In einem Vorfahren
|
||
mit `overflow` würde die Liste geklippt (D50). Die IDs in der
|
||
Mono-Schrift des Textfelds, der Titel als gedämpfter Kontext daneben. */
|
||
.aclist{
|
||
position:fixed;z-index:70;min-width:160px;max-width:min(24rem, calc(100vw - 16px));
|
||
max-height:12.5rem;overflow-y:auto;
|
||
background:var(--card);border:1px solid rgba(36,52,71,.15);border-radius:8px;
|
||
box-shadow:0 8px 24px rgba(36,52,71,.25);padding:4px;
|
||
}
|
||
.aclist[hidden]{display:none}
|
||
.acitem{
|
||
display:flex;align-items:baseline;gap:8px;padding:4px 8px;border-radius:5px;
|
||
cursor:pointer;white-space:nowrap;
|
||
}
|
||
.acitem.sel,.acitem:hover{background:rgba(15,118,110,.12)}
|
||
.acid{font-family:'IBM Plex Mono',monospace;font-size:.74rem;color:var(--ink)}
|
||
.aclabel{
|
||
font-size:.7rem;color:var(--muted);
|
||
overflow:hidden;text-overflow:ellipsis;max-width:14rem;
|
||
}
|
||
/* Visuell versteckt, für Screenreader da — die Live-Region der Vorschläge. */
|
||
.vh{
|
||
position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;
|
||
clip-path:inset(50%);white-space:nowrap;
|
||
}
|
||
|
||
/* --- ALL OF: Kinder nebeneinander, durchgezogener Verteiler --- */
|
||
ul.and{display:flex;align-items:flex-start;position:relative;padding-top:14px}
|
||
ul.and::before{
|
||
content:'';position:absolute;top:0;left:50%;height:14px;border-left:2px solid var(--line);
|
||
}
|
||
ul.and>li{padding:16px 12px 0}
|
||
/* Der Stiel liegt bei `--stem-x` — normalerweise 50 % des <li>, was die
|
||
Knotenmitte ist, solange der Knoten im <li> zentriert steht. Bei
|
||
`li.has-or` ist er das NICHT (linksbündig, damit die any-of-Leiste unter
|
||
ihm aufsetzt) und der Teilbaum macht das <li> breiter als den Knoten: der
|
||
Stiel liefe daneben vorbei. `alignStems()` in app.js misst dort die
|
||
Knotenmitte und setzt `--stem-x` — D10 in der Waagerechten. */
|
||
ul.and>li::before,
|
||
ul.and>li::after{
|
||
content:'';position:absolute;top:0;
|
||
left:0;right:calc(100% - var(--stem-x, 50%));
|
||
border-top:2px solid var(--line);height:16px;
|
||
}
|
||
ul.and>li::after{left:var(--stem-x, 50%);right:0;border-left:2px solid var(--line)}
|
||
ul.and>li:first-child::before,
|
||
ul.and>li:last-child::after{border:0 none}
|
||
ul.and>li:last-child::before{border-right:2px solid var(--line);border-radius:0 6px 0 0}
|
||
ul.and>li:first-child::after{border-radius:6px 0 0 0}
|
||
/* Einziges Kind: Normalerweise fallen der Stiel aus dem Elternknoten
|
||
(`ul.and::before`, 50 % der Gruppe) und der Stiel zum Kind (`--stem-x`)
|
||
zusammen — eine gerade Linie, keine Leiste nötig. Steht der Knoten aber
|
||
nicht mittig in seiner Zelle (`li.has-or`, Treppe — dort misst
|
||
`alignStems()` die Knotenmitte, D29-Nachtrag 2), liegen die beiden
|
||
auseinander, und ohne Geschwister gibt es keine Sammelleiste, die sie
|
||
verbände: die Linie riss ab (gemessen: 8,5 px). Deshalb hier ein kurzes
|
||
Leiterstück genau zwischen den beiden — dieselbe L-Form, mit der ein
|
||
äußeres Kind einer größeren Gruppe angeschlossen ist. Ohne `--stem-x` ist
|
||
es null Pixel breit und damit unsichtbar. Diese Regeln müssen NACH den
|
||
first/last-Regeln stehen: `li:only-child` ist beides, und bei gleicher
|
||
Spezifität gewinnt die spätere. */
|
||
ul.and>li:only-child{padding-top:14px}
|
||
ul.and>li:only-child::before{
|
||
border:0 none;border-top:2px solid var(--line);border-radius:0;height:14px;
|
||
left:min(50%, var(--stem-x, 50%));
|
||
right:calc(100% - max(50%, var(--stem-x, 50%)));
|
||
}
|
||
ul.and>li:only-child::after{
|
||
border:0 none;border-left:2px solid var(--line);border-radius:0;height:14px;
|
||
left:var(--stem-x, 50%);right:0;
|
||
}
|
||
|
||
/* --- ANY OF: durchgehend gestrichelte graue Linien (Haupt- + Abzweige) --- */
|
||
li.has-or{align-items:flex-start}
|
||
ul.or{
|
||
display:flex;flex-direction:column;
|
||
align-items:flex-start;
|
||
position:relative;
|
||
padding-top:14px;
|
||
}
|
||
ul.or::before{
|
||
content:'';position:absolute;top:0;left:9px;height:14px;border-left:2px dashed var(--muted);
|
||
}
|
||
/* XOR (`=`, SPEC §3/§9, D35): Geometrie kommt komplett von `ul.or` (der
|
||
Renderer gibt `class="or xor"` aus); `.xor` ergänzt nur die „1"-Plakette
|
||
am Austritt der Sammelleiste — „genau eine". Grau wie die any-of-Führung
|
||
(Gate-Codierung, keine Signalfarbe, D15); sitzt mittig auf dem
|
||
14-px-Einlaufstück (left:9px = Leisten-x in allen Modi). */
|
||
ul.xor::after{
|
||
content:'1';
|
||
position:absolute;left:9px;top:7px;transform:translate(-50%,-50%);
|
||
width:13px;height:13px;border-radius:50%;
|
||
background:var(--card);border:1.5px solid var(--muted);
|
||
box-sizing:border-box;
|
||
color:var(--muted);font-size:9px;font-weight:600;line-height:10px;
|
||
text-align:center;
|
||
}
|
||
ul.or>li{position:relative;align-items:flex-start;padding:5px 0 5px 30px}
|
||
/* Abzweig auf Höhe der Knoten-Mitte (5px li-Padding + halbe Knotenhöhe), nicht Mitte des Teilbaums */
|
||
ul.or>li::before{
|
||
content:'';position:absolute;left:9px;top:23px;width:21px;border-top:2px dashed var(--muted);
|
||
}
|
||
ul.or>li::after{
|
||
content:'';position:absolute;left:9px;top:0;height:100%;border-left:2px dashed var(--muted);
|
||
}
|
||
ul.or>li:last-child::after{height:23px}
|
||
/* all-of-Teilbaum UNTER einer any-of-Alternative (nur horizontal):
|
||
schmal transponiert stapeln — Kinder untereinander mit linkem, solidem
|
||
Verteiler (wie im kompakten Modus), statt breitem Fächer. Sonst zwingt der
|
||
breite Fächer ein Zentrieren, das den Alternativknoten (und damit den
|
||
ganzen Elternbaum) weit nach rechts schiebt. Passt zugleich stilistisch
|
||
zur gestapelten any-of-Spalte darüber. Vertikal/kompakt haben ihre eigene
|
||
`ul.and`-Behandlung und bleiben unberührt. */
|
||
.tree:not(.vertical):not(.kompakt) ul.or>li.has-and>ul.and{
|
||
flex-direction:column;align-items:flex-start;padding-top:14px;
|
||
}
|
||
.tree:not(.vertical):not(.kompakt) ul.or>li.has-and>ul.and::before{left:9px;height:14px}
|
||
.tree:not(.vertical):not(.kompakt) ul.or>li.has-and>ul.and>li{padding:5px 0 5px 30px}
|
||
.tree:not(.vertical):not(.kompakt) ul.or>li.has-and>ul.and>li::before{
|
||
left:9px;right:auto;top:23px;width:21px;height:0;
|
||
border:0;border-top:2px solid var(--line);border-radius:0;
|
||
}
|
||
.tree:not(.vertical):not(.kompakt) ul.or>li.has-and>ul.and>li::after{
|
||
left:9px;right:auto;top:0;width:0;height:100%;
|
||
border:0;border-left:2px solid var(--line);border-radius:0;
|
||
}
|
||
.tree:not(.vertical):not(.kompakt) ul.or>li.has-and>ul.and>li:last-child::after{height:23px}
|
||
ul.or .node{
|
||
border-color:var(--muted);
|
||
box-shadow:none;
|
||
font-weight:400;
|
||
}
|
||
|
||
/* --- OPTIONAL (`+`, SPEC §3/D29): gestrichelter Abzweig + hohler Kreis ---
|
||
Gestrichelt wird NUR der Abzweig zum Knoten, nicht die Sammelleiste — die
|
||
gehört der ganzen Geschwistergruppe. Im Fächer ist der Abzweig der
|
||
senkrechte Stiel (border-left von ::after, beim letzten Kind border-right
|
||
von ::before), in den gestapelten Anordnungen der waagerechte Ast
|
||
(border-top von ::before); die Leiste ist jeweils die andere Kante.
|
||
Gestrichelt in TINTE — any-of ist gestrichelt in Grau (D15), die Farbe
|
||
hält beide auseinander, der Kreis unten bestätigt es. */
|
||
ul.and>li.opt::after{border-left-style:dashed}
|
||
ul.and>li.opt:last-child::before{border-right-style:dashed}
|
||
.tree.vertical ul.and>li.opt::before,
|
||
.tree.kompakt ul.and>li.opt::before,
|
||
.tree:not(.vertical):not(.kompakt) ul.or>li.has-and>ul.and>li.opt::before{
|
||
border-top-style:dashed;
|
||
}
|
||
|
||
/* Hohler Kreis am Abzweig ---
|
||
Kein dritter Linienstil: gestrichelt gehört any-of, und im kompakten Modus
|
||
codiert allein der Stil das Gate (D15). Stattdessen die FODA-Konvention —
|
||
gefüllter Punkt = erforderlich (hier: gar keiner), hohler Punkt = optional.
|
||
Der Kreis sitzt MITTIG AUF der Knotenkante, wo der Abzweig auftrifft.
|
||
Grundfall ist die gestapelte Anordnung (vertikal, kompakt, unter any-of):
|
||
Abzweig kommt von links auf halber Knotenhöhe. */
|
||
.node.opt::before{
|
||
content:'';position:absolute;
|
||
/* 18px statt 50%: Der gestapelte Abzweig sitzt fest bei 23px im <li>
|
||
(= 5px Listenabstand + halbe EINZEILIGE Knotenhöhe). Seit Knoten
|
||
umbrechen (D64), ist 50% bei mehrzeiligen nicht mehr diese Höhe —
|
||
der Kreis muss auf dem Abzweig sitzen, nicht auf der Knotenmitte.
|
||
Für einzeilige Knoten sind 18px und 50% dasselbe. */
|
||
left:-5px;top:18px;transform:translateY(-50%);
|
||
width:10px;height:10px;border-radius:50%;
|
||
background:var(--card);border:2px solid var(--line);
|
||
box-sizing:border-box;
|
||
}
|
||
/* Horizontaler Fächer: der Abzweig kommt von oben mittig. */
|
||
.tree:not(.vertical):not(.kompakt) ul.and>li>.node.opt::before{
|
||
left:50%;top:-5px;transform:translateX(-50%);
|
||
}
|
||
/* …ausgenommen der gestapelte all-of-Teilbaum unter einer any-of-Gruppe
|
||
(siehe oben): dort kommt der Abzweig wieder von links. */
|
||
.tree:not(.vertical):not(.kompakt) ul.or>li.has-and>ul.and>li>.node.opt::before{
|
||
left:-5px;top:18px;transform:translateY(-50%);
|
||
}
|
||
/* Vertikal zentrierte all-of-Zwischenknoten (unten): dort trifft der Abzweig
|
||
wirklich die Knotenmitte (50%) — der Kreis geht mit. */
|
||
.tree.vertical li.has-and>ul.and>li.has-and>.node.opt::before{top:50%}
|
||
|
||
/* --- TREPPE: aufeinanderfolgende optionale Endknoten (D29, Nachtrag 3) ---
|
||
Nur im horizontalen Fächer, wo jedes Geschwister sonst eine eigene Spalte
|
||
kostet. `applyOptStairs()` in app.js gruppiert sie in
|
||
`li.opt-group > ul.opt-stair` und nummeriert die Stufen als `--i` (0-basiert).
|
||
Geometrie: Stufe 0 hängt am Stiel von oben wie ein gewöhnliches Kind
|
||
(`--stem-x` zielt auf sie). Jede weitere Stufe steht `--stair-step` weiter
|
||
rechts und hängt an einem gestrichelten Winkel, der bündig an der linken
|
||
Kante der VORIGEN Stufe herabfällt und waagerecht in den eigenen Kreis
|
||
einbiegt. Das setzt voraus, dass die Zelle so hoch ist wie ihr Knoten —
|
||
deshalb nur Endknoten. */
|
||
.tree:not(.vertical):not(.kompakt) ul.opt-stair{
|
||
--stair-step:26px; --stair-gap:12px;
|
||
display:flex;flex-direction:column;align-items:flex-start;position:relative;
|
||
}
|
||
.tree:not(.vertical):not(.kompakt) ul.opt-stair>li{
|
||
align-items:flex-start;
|
||
padding:var(--stair-gap) 0 0 calc(var(--i) * var(--stair-step));
|
||
}
|
||
.tree:not(.vertical):not(.kompakt) ul.opt-stair>li:first-child{padding-top:0}
|
||
/* Winkel zur vorigen Stufe: senkrecht herab, dann waagerecht in den Knoten.
|
||
Der Knick liegt auf der linken Kante der vorigen Stufe. */
|
||
.tree:not(.vertical):not(.kompakt) ul.opt-stair>li+li::before,
|
||
.tree:not(.vertical):not(.kompakt) ul.opt-stair>li+li::after{
|
||
content:'';position:absolute;
|
||
left:calc((var(--i) - 1) * var(--stair-step));
|
||
}
|
||
.tree:not(.vertical):not(.kompakt) ul.opt-stair>li+li::after{
|
||
top:0;height:calc(var(--stair-gap) + 16px);
|
||
border-left:2px dashed var(--line);
|
||
}
|
||
.tree:not(.vertical):not(.kompakt) ul.opt-stair>li+li::before{
|
||
top:calc(var(--stair-gap) + 16px);width:var(--stair-step);
|
||
border-top:2px dashed var(--line);
|
||
}
|
||
/* Stufe 0 wird von oben getroffen (Kreis oben mittig), alle weiteren von
|
||
links — das ist bereits der Grundfall von `.node.opt::before`. */
|
||
.tree:not(.vertical):not(.kompakt) ul.opt-stair>li:first-child>.node.opt::before{
|
||
left:50%;top:-5px;transform:translateX(-50%);
|
||
}
|
||
|
||
/* ---------- Status (Pastell) ---------- */
|
||
.node.st-idee {background:#EBEDEF;border-color:#A2ABB5;color:var(--ink)}
|
||
.node.st-geplant {background:#EBE4F6;border-color:#A991D4;color:var(--ink)}
|
||
.node.st-arbeit {background:#FADDE4;border-color:#D897A8;color:var(--ink)}
|
||
.node.st-durchstich{background:#FBF2CE;border-color:#D9BE63;color:var(--ink)}
|
||
.node.st-fertig {background:#DCF1DE;border-color:#86C293;color:var(--ink)}
|
||
.node.st-prod {background:#DBEAF8;border-color:#85ACD7;color:var(--ink)}
|
||
.node.st-highrisk {background:#FFE5CC;border-color:#F97316;color:var(--ink)}
|
||
.node.st-verworfen{
|
||
background:#F1F2F4;border-color:#B3BAC2;border-style:dashed;
|
||
color:var(--muted);text-decoration:line-through;box-shadow:none;
|
||
}
|
||
|
||
.chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
|
||
.chip{
|
||
font-family:'IBM Plex Mono',monospace;font-size:.72rem;
|
||
border:1.5px solid;border-radius:6px;padding:2px 8px;
|
||
color:var(--ink);white-space:nowrap;
|
||
}
|
||
.chip.st-idee {background:#EBEDEF;border-color:#A2ABB5}
|
||
.chip.st-geplant {background:#EBE4F6;border-color:#A991D4}
|
||
.chip.st-arbeit {background:#FADDE4;border-color:#D897A8}
|
||
.chip.st-durchstich{background:#FBF2CE;border-color:#D9BE63}
|
||
.chip.st-fertig {background:#DCF1DE;border-color:#86C293}
|
||
.chip.st-prod {background:#DBEAF8;border-color:#85ACD7}
|
||
.chip.st-highrisk {background:#FFE5CC;border-color:#F97316}
|
||
.chip.st-verworfen{background:#F1F2F4;border-color:#B3BAC2;border-style:dashed;color:var(--muted);text-decoration:line-through}
|
||
|
||
/* ---------- Transponierte Modi (vertikal + kompakt) ---------- */
|
||
/* Gemeinsame Basis: Kinder untereinander, Sammelleiste links,
|
||
Abzweig auf Knotenmitte (23-px-Offset). */
|
||
.tree.vertical, .tree.kompakt{margin:0}
|
||
.tree.vertical li, .tree.kompakt li{align-items:flex-start}
|
||
.tree.vertical ul.and, .tree.kompakt ul.and{--rail:9px;flex-direction:column;align-items:flex-start;padding-top:14px}
|
||
.tree.vertical ul.and::before, .tree.kompakt ul.and::before{left:var(--rail);height:14px}
|
||
.tree.vertical ul.and>li, .tree.kompakt ul.and>li{padding:5px 0 5px calc(var(--rail) + 21px)}
|
||
.tree.vertical ul.and>li::before, .tree.kompakt ul.and>li::before{
|
||
left:var(--rail);right:auto;top:23px;width:21px;height:0;
|
||
border:0;border-top:2px solid var(--line);border-radius:0;
|
||
}
|
||
.tree.vertical ul.and>li::after, .tree.kompakt ul.and>li::after{
|
||
left:var(--rail);right:auto;top:0;width:0;height:100%;
|
||
border:0;border-left:2px solid var(--line);border-radius:0;
|
||
}
|
||
.tree.vertical ul.and>li:last-child::after, .tree.kompakt ul.and>li:last-child::after{height:23px}
|
||
|
||
/* Nur vertikal: echter transponierter, ZENTRIERTER LR-Baum — analog zur
|
||
horizontalen Darstellung, nur um 90° gedreht. Der Überknoten sitzt
|
||
vertikal mittig zu seiner Kindergruppe; die all-of-Verbindung tritt rechts
|
||
auf halber Höhe aus, eine vertikale Sammelleiste verteilt auf die Kinder.
|
||
Kompakt bleibt unberührt (führt all-of nach unten). */
|
||
.tree.vertical li.has-and{flex-direction:row;align-items:center}
|
||
/* Der Stub ist hier länger als die 9 px der gemeinsamen Basis: Das
|
||
Größen-Badge (oben rechts) und die Tag-Pillen (unten rechts) ragen 8 px
|
||
über die Knotenkante hinaus und standen mit 1 px Luft an der
|
||
Sammelleiste — mit ihrem weißen Rand fraßen sie die Linie optisch auf.
|
||
20 px lassen 12 px Luft. Nur vertikal: kompakt führt all-of nach unten,
|
||
dort treffen Badge und Leiste nie aufeinander. */
|
||
.tree.vertical li.has-and>ul.and{--rail:20px;padding-top:0}
|
||
/* Stub von der Parent-Mitte (halbe Höhe) zur vertikalen Sammelleiste */
|
||
.tree.vertical li.has-and>ul.and::before{
|
||
left:0;top:50%;width:var(--rail);height:0;
|
||
border-left:0;border-top:2px solid var(--line);
|
||
}
|
||
/* Abzweig trifft die tatsächliche Knotenmitte des Kindes:
|
||
Blatt- und Any-of-Kinder sitzen oben (Offset 23 px), all-of-Zwischenknoten
|
||
sind selbst vertikal zentriert (50 %). Ohne diese Unterscheidung liefen die
|
||
Linien zu zentrierten Zwischenknoten ins Leere. Die Enden der Sammelleiste
|
||
folgen demselben Abzweigpunkt des jeweiligen Rand-Kindes. */
|
||
.tree.vertical li.has-and>ul.and>li:first-child::after{top:23px;height:calc(100% - 23px)}
|
||
/* Einziges Kind: KEIN border:0 mehr (D65). Der Eltern-Stub dockt bei 50 % der
|
||
Gruppenhöhe an, der Kind-Abzweig liegt fest bei 23 px — seit dem
|
||
20-px-Zusatzabstand unten (und erst recht bei mehrzeiligen Knoten, D64)
|
||
fallen die beiden NICHT mehr zusammen. Das Verbindungsstück ist hier rein
|
||
in CSS ausdrückbar, weil Gruppenhöhe == Kindhöhe (ul hat padding-top:0). */
|
||
.tree.vertical li.has-and>ul.and>li:only-child::after{top:23px;height:max(0px, calc(50% - 23px))}
|
||
/* Letztes Kind mit großem Teilbaum (D65): Die Leiste endet am 23-px-Abzweig,
|
||
der Eltern-Stub dockt aber bei 50 % der GRUPPE an — liegt der tiefer
|
||
(großer Teilbaum im letzten Kind), hinge er in der Luft. `alignVRails()`
|
||
in app.js misst das und verlängert die Leiste per --vrail-ext bis zum
|
||
Stub. has-and-Kinder brauchen das nie (Abzweig bei 50 % der Zelle, und
|
||
50 % der Gruppe liegt beweisbar darüber). */
|
||
.tree.vertical li.has-and>ul.and>li:last-child:not(.has-and):not(:only-child)::after{height:var(--vrail-ext, 23px)}
|
||
.tree.vertical li.has-and>ul.and>li.has-and::before{top:50%}
|
||
.tree.vertical li.has-and>ul.and>li.has-and:first-child::after{top:50%;height:50%}
|
||
.tree.vertical li.has-and>ul.and>li.has-and:last-child::after{top:0;height:50%}
|
||
.tree.vertical li.has-and>ul.and>li.has-and:only-child::after{border:0}
|
||
|
||
/* Mehr Luft zwischen gestapelten Knoten (transponierte Modi): sonst
|
||
überlappt das Größen-Badge (oben rechts) eines Knotens mit den
|
||
Namens-Tags (unten rechts) des Knotens darüber. Nur zusätzlicher Abstand
|
||
NACH unten, damit der obere 23-px-Abzweig unverändert bleibt. */
|
||
.tree.kompakt ul.and>li,
|
||
.tree.kompakt ul.or>li,
|
||
.tree.vertical ul.or>li,
|
||
.tree.vertical ul.and>li:not(.has-and){
|
||
padding-bottom:20px;
|
||
}
|
||
/* Zentrierte all-of-Zwischenknoten (nur vertikal) brauchen den Abstand
|
||
symmetrisch, damit ihr 50%-Abzweig auf der Knotenmitte bleibt. */
|
||
.tree.vertical li.has-and>ul.and>li.has-and{
|
||
padding-top:16px;padding-bottom:16px;
|
||
}
|
||
/* Vertikal + any-of-Alternative, die selbst all-of zerlegt (z. B. „Web+Nativ“):
|
||
Der Knoten sitzt vertikal mittig zu seinem Rechts-Fächer, also muss auch der
|
||
EINKOMMENDE any-of-Abzweig auf die Knotenmitte (50 %) zeigen statt auf 23 px —
|
||
sonst ist der Knoten von seiner Linie von oben abgetrennt. Symmetrisches
|
||
Padding hält die Mitte bei 50 % (überschreibt das asymmetrische ul.or>li). */
|
||
.tree.vertical ul.or>li.has-and{padding-top:16px;padding-bottom:16px}
|
||
.tree.vertical ul.or>li.has-and::before{top:50%}
|
||
.tree.vertical ul.or>li.has-and:last-child::after{height:50%}
|
||
|
||
/* Kompakt: auch All-of läuft nach unten (spart Breite) — kein Rechts-Fächer.
|
||
Die graue, gestrichelte Any-of-Führung kommt aus der Basis und gilt hier
|
||
wie in allen Modi. Gate-Codierung: und = durchgezogen dunkel (Tinte),
|
||
oder = gestrichelt grau. */
|
||
|
||
/* ---------- Kleiner Bildschirm (Smartphone) ----------
|
||
body.mobile wird per matchMedia (<=640px) gesetzt. Es ist **immer genau ein
|
||
Bereich** zu sehen (Diagramm ODER Text); oben links schaltet ein Knopf um.
|
||
Die Min/Normal/Max-Buttons und der Splitter entfallen hier ganz — es gibt
|
||
nichts zu teilen. Die Legende bekommt einen eigenen Umschalter statt der
|
||
dauerhaften „AGENDA“-Zeile. Siehe D17-Nachtrag. */
|
||
body.mobile .winbtns{display:none}
|
||
/* Ein Bereich füllt die Fläche; der andere ist `display:none` (nicht bloß
|
||
zusammengeschoben) — nur so bekommt der sichtbare die ganze Höhe, ohne
|
||
dass eine zweite Titelzeile sie anknabbert. Die Kehrseite steht in
|
||
app.js: Ein verborgenes Diagramm misst sich zu 0, deshalb wird die
|
||
Geometrie beim Zurückschalten neu gezeichnet. */
|
||
body.mobile .app,
|
||
body.mobile .app.side{
|
||
grid-template-columns:minmax(0,1fr);
|
||
grid-template-rows:minmax(0,1fr);
|
||
grid-template-areas:"pane";
|
||
}
|
||
body.mobile .gutter{display:none}
|
||
body.mobile .panel.editor,
|
||
body.mobile .panel.right{grid-area:pane}
|
||
body.pane-diagram.mobile .panel.editor{display:none}
|
||
body.pane-text.mobile .panel.right{display:none}
|
||
/* Der Bereichstitel „DIAGRAMM" entfällt — welcher Bereich vorn ist, sagt der
|
||
Inhalt; die Zeile wird für den Umschalter und die Aktionen gebraucht. */
|
||
body.mobile .panel.right .panel-head>span[data-i18n="diagramTitle"]{display:none}
|
||
/* Umschalter oben links; er übernimmt vom entfallenen Titel das
|
||
`margin-right:auto`, damit die Aktions-Knöpfe rechts stehen bleiben. Im
|
||
Editor-Kopf tut das die Stand-Gruppe daneben (D81). */
|
||
body.mobile .panetoggle{display:inline-flex}
|
||
body.mobile .panel.right .panetoggle{margin-right:auto}
|
||
/* Die Titelzeile muss EINZEILIG bleiben (D17): Bricht sie um, wächst der
|
||
Kopf auf das Doppelte und frisst die Fläche, für die es den
|
||
Ein-Bereich-Modus überhaupt gibt. Die Knöpfe passen längst — es waren die
|
||
Abstände: 14 px Lücke mal sieben plus 2×16 px Innenabstand sind mehr als
|
||
ein Drittel von 375 px. Deshalb hier enger und `nowrap` als Riegel; die
|
||
Knöpfe selbst bleiben unangetastet (Fingergröße).
|
||
Mit dem neunten Bedienelement (Stationssprung, D47) noch einmal enger:
|
||
6 px Lücke und 8 px Innenabstand. Dazu `flex:0 0 auto` — ohne das schrumpft
|
||
ausgerechnet der Modus-Wähler (das einzige Element ohne feste Größe) bei
|
||
Platzmangel auf einen 2-px-Strich zusammen, während sein Icon darüber
|
||
hinausragt. Ein zerdrücktes Bedienelement ist der schlechtere Fehler als
|
||
ein sichtbarer Überlauf (D17-Nachtrag 5). */
|
||
body.mobile .panel-head{gap:6px;flex-wrap:nowrap;padding-inline:8px}
|
||
body.mobile .panel-head>*{flex:0 0 auto}
|
||
/* Unterhalb von rund 440 px ist es reine Arithmetik: **zehn** Bedienelemente
|
||
à 29 px (D56 hat den ID-Umschalter hinzugefügt) plus Lücken brauchen
|
||
386 px, bei 375 px stehen 359 zur Verfügung. Kleiner als 29 px darf ein
|
||
Fingerziel nicht werden (D17-Nachtrag 5) — dort darf die Zeile deshalb
|
||
umbrechen. Sie tut es aber nur, wenn wirklich alle zehn da sind: Der
|
||
„Was ist neu?"-Knopf (44 px) erscheint nur bei Neuigkeiten, ohne ihn
|
||
bleiben 336 px und damit eine Reihe. Die Schwelle stand vorher bei 360 px
|
||
und ist mit dem zehnten Element mitgewandert — dieselbe Rechnung, ein
|
||
Element mehr.
|
||
**Kein `overflow` an dieser Stelle** (D50): Die Kopfzeile trägt die beiden
|
||
Aufklapp-Menüs (Dokumente, Download) als absolut positionierte Kinder. Ein
|
||
Scroll-Container klippt sie — und weil `overflow-x` das `visible` der
|
||
anderen Achse auf `auto` hebt, klippt er auch nach unten, wo die Menüs
|
||
stehen. Sie waren damit auf **jeder** Mobilbreite unerreichbar, nicht nur
|
||
auf den schmalen. */
|
||
@media (max-width:440px){
|
||
body.mobile .panel-head{flex-wrap:wrap}
|
||
}
|
||
/* Inhalte rund 25 % kleiner, damit mehr Plan auf die Fläche passt
|
||
(D17-Nachtrag 2). Das Diagramm macht das über den CSS-`zoom` (Faktor auf
|
||
den Nutzer-Zoom, siehe MOBILE_ZOOM in app.js); der Text über die
|
||
Schriftgröße — `zoom` verbietet sich hier, weil Zeilennummern-Streifen und
|
||
Spiegel am Textfeld messen (D33) und der Streifen seine Breite in `ch`
|
||
rechnet. Beide müssen dieselbe Typografie tragen, sonst wandern die Zahlen
|
||
weg; `line-height` ist einheitenlos und skaliert von selbst mit. */
|
||
body.mobile textarea,
|
||
body.mobile .lineno{font-size:.64rem}
|
||
body.mobile textarea{padding:10px 12px}
|
||
/* Der Dokumentname übernimmt die Untertitel-Zeile (D81): Die Brotkrume
|
||
bricht unter die Marke um und bekommt dort die volle Breite — lange
|
||
URL-Namen brauchen sie. Der Untertitel entfällt; es kommt keine Fläche
|
||
hinzu. Das „›" ohne „Werkbaum" davor sagte nichts und entfällt mit. */
|
||
body.mobile header p{display:none}
|
||
/* Die Marken-Gruppe löst sich auf (display:contents): Sie ist nur so breit,
|
||
wie die Werkzeuge rechts ihr lassen (~170 px) — Chip-Zeile und Menü
|
||
brauchen aber die volle Kopfbreite. Als direkte Kinder der Kopfzeile
|
||
bricht der Chip per order:3 unter Marke und Werkzeuge um, und das Menü
|
||
nimmt die Kopfzeile als Bezug (dieselbe Verlegung wie beim
|
||
Neuigkeiten-Popup, D58). */
|
||
body.mobile .brandgroup{display:contents}
|
||
body.mobile .crumb-sep{display:none}
|
||
/* Marke und Werkzeuge müssen sich EINE Zeile teilen (sonst wächst der Kopf
|
||
auf drei Zeilen — neue Fläche, die es nicht geben darf). Wie in den
|
||
Titelzeilen (D17-Nachtrag 5) weichen die Lücken, nicht die Knöpfe:
|
||
gemessen 164 px Marke + 164 px Werkzeuge bei 335 px Breite. */
|
||
body.mobile .header-row{position:relative;flex-wrap:wrap;row-gap:4px;column-gap:8px}
|
||
body.mobile .header-tools{gap:6px}
|
||
/* Nachgemessen mit Neuigkeiten-Zähler UND Build-Hinweis (die beiden
|
||
Gelegenheits-Breiten, D17-Lektion: mit dem versteckten Element messen):
|
||
ohne diese zwei Handgriffe stand die Summe exakt auf der Kante. Die
|
||
Sprachpille bleibt über 29 px Fingergröße. */
|
||
body.mobile .langsel button{padding:5px 8px}
|
||
body.mobile header h1{gap:8px}
|
||
body.mobile .docwrap{flex:1 1 100%;order:3;position:static}
|
||
body.mobile .doctrigger{max-width:100%}
|
||
body.mobile .docname{max-width:none}
|
||
body.mobile .docmenu{left:0;right:0;width:auto}
|
||
/* Die Editor-Beschriftung entfällt wie der Bereichstitel „DIAGRAMM" — die
|
||
Zeile wird für Umschalter und Stand-Knöpfe gebraucht; das auto-Margin
|
||
trägt die Stand-Gruppe. */
|
||
body.mobile .panel.editor .panel-head>span[data-i18n="editorTitle"]{display:none}
|
||
/* Editor immer gestapelt (Textarea oben, Legende darunter) */
|
||
body.mobile .editor-body{flex-direction:column;align-items:stretch}
|
||
body.mobile .agenda{border-left:0;border-top:1px dashed rgba(36,52,71,.18)}
|
||
body.mobile .agenda.open{width:auto;max-width:none;height:var(--hrow,220px);max-height:85%}
|
||
body.mobile .hint-gutter{cursor:row-resize}
|
||
body.mobile .hint-gutter::before{width:38px;height:3px}
|
||
/* Legende: keine dauerhafte Summary-Zeile, dafür der #legendBtn im Kopf */
|
||
body.mobile .agenda-summary{display:none}
|
||
body.mobile .legendbtn{display:inline-flex}
|
||
/* Sprachwahl schlank: eingeklappt nur die aktive Sprache. Ein Tipp klappt die
|
||
volle Leiste als Overlay über die Kopfzeile auf (verdeckt fsToggle usw.,
|
||
statt die Zeile zu verbreitern); nach der Auswahl klappt sie wieder ein
|
||
(JS). Das „…“ entfällt hier — die aktive Sprache ist selbst der Auslöser. */
|
||
body.mobile .header-tools{position:relative}
|
||
/* Das Neuigkeiten-Popup ist breiter als sein Knopf. Am Knopf ausgerichtet
|
||
(`.newswrap{position:relative}`) begänne es auf dem Telefon bei −116 px,
|
||
also außerhalb des Bildes — nachgemessen bei 375 px. Hier zählt deshalb
|
||
die **Werkzeugleiste** als Bezug: Ihr rechter Rand ist auch der der Seite.
|
||
Kein `overflow` an der Kopfzeile als Ausweg — das klippt die Menüs (D50). */
|
||
body.mobile .newswrap{position:static}
|
||
body.mobile .langsel button[data-lang]{display:none}
|
||
body.mobile .langsel button[data-lang].active{display:inline-block}
|
||
body.mobile .langmore{display:none}
|
||
body.mobile .langsel.expanded{
|
||
position:absolute;top:0;right:0;z-index:40;
|
||
background:var(--card);
|
||
box-shadow:0 6px 18px rgba(36,52,71,.28);
|
||
}
|
||
body.mobile .langsel.expanded button[data-lang]{display:inline-block}
|
||
/* Modus-Wähler kompakt: nur das aktive Icon (ein Drittel Platz), Tippen
|
||
schaltet reihum weiter (horizontal → kompakt → vertikal → …, siehe JS). */
|
||
body.mobile .seg label{display:none}
|
||
body.mobile .seg label:has(input:checked){display:flex}
|
||
/* Download schlank: nur der Trigger-Icon-Button; ein Tipp klappt SVG/PNG als
|
||
Dropdown-Overlay unter der Kopfzeile auf (verhindert den Zeilenumbruch durch
|
||
die breiten „SVG“/„PNG“-Labels). Kopieren bleibt sichtbar. */
|
||
body.mobile .dlgroup{position:relative}
|
||
body.mobile .dltrigger{display:inline-flex}
|
||
body.mobile .dltrigger[aria-expanded="true"]{border-color:var(--or);color:var(--or)}
|
||
body.mobile .dlmenu{display:none}
|
||
body.mobile .dlgroup.open .dlmenu{
|
||
display:flex;flex-direction:column;gap:5px;
|
||
position:absolute;top:calc(100% + 6px);right:0;z-index:40;
|
||
background:var(--card);border:1px solid rgba(36,52,71,.15);
|
||
border-radius:8px;padding:5px;box-shadow:0 6px 18px rgba(36,52,71,.28);
|
||
}
|
||
|
||
/* ---------- Druck: nur das Diagramm (SPEC §9, TASKS Phase 2) ----------
|
||
Editor-Panel, Bedienelemente, Splitter, Kopfzeile, Warnungen und Footer
|
||
verschwinden; das Diagramm füllt die Seite und darf über mehrere Seiten
|
||
laufen. Statusfarben werden bewusst mitgedruckt (Browser strippen
|
||
Hintergründe sonst). Der Günstigster-Pfad-Overlay liegt in #out und wird
|
||
mitgedruckt. */
|
||
@media print{
|
||
html,body{height:auto!important;overflow:visible!important;background:#fff!important}
|
||
header,.panel.editor,.gutter,.panel.right .panel-head,
|
||
.warnings,.zoomctl,.site-footer,.nodetip,.aclist,.peoplebar{display:none!important}
|
||
.app,.app.side{display:block!important;max-width:none!important;width:auto!important;
|
||
height:auto!important;margin:0!important;gap:0!important}
|
||
.panel.right{grid-area:auto!important;background:#fff!important;border:0!important;
|
||
box-shadow:none!important;border-radius:0!important;
|
||
height:auto!important;min-height:0!important;overflow:visible!important}
|
||
.diagram{overflow:visible!important;height:auto!important;padding:8px 0!important}
|
||
/* Statusfarben & Pfad-Töne mitdrucken */
|
||
*{-webkit-print-color-adjust:exact!important;print-color-adjust:exact!important}
|
||
/* Knoten nicht über den Seitenrand zerschneiden */
|
||
#out li{break-inside:avoid}
|
||
/* Die Abweichungs-Marke hängt an der Sitzung (Anmeldung, Abrufzeit) —
|
||
gedruckt wird der Plan, nicht mein Abrufstand (D91-Nachtrag 10). */
|
||
.node .tref-diff{color:inherit!important;font-weight:inherit!important}
|
||
.node .tref-badge{display:none!important}
|
||
/* Editierhilfe bzw. Zuruf, nicht drucken (D25, Fokusmarke SPEC §1).
|
||
Bei der Cursor-Zeile gehören Erhebung und Puls mit dazu — sonst stünde ein
|
||
Knoten im Druck grundlos vergrößert und schief zu seiner Anschlusslinie. */
|
||
.node.current,.node.focusmark{box-shadow:none!important}
|
||
.node.current{transform:none!important;animation:none!important}
|
||
.node.current::after{display:none!important}
|
||
.node.fresh{box-shadow:none!important} /* „neu seit dem letzten Besuch" ist persönlich (D28) */
|
||
.node.tipped{box-shadow:none!important} /* offenes Knoten-Fenster ist Bedienung (D52) */
|
||
/* Faltung (D38): das ▾ offener Knoten ist Bedienelement — nicht drucken;
|
||
das „▸ n" eingeklappter Knoten ist Kennzeichnung und bleibt. */
|
||
.node:not(.folded) .fold{display:none!important}
|
||
@page{margin:12mm}
|
||
}
|
||
|
||
/* ---------- Konflikt beim gemeinsamen Bearbeiten (D76) ----------
|
||
Band oben, wie der Update-Hinweis: Es unterbricht nicht die Arbeit, steht
|
||
aber über allem, bis jemand entscheidet. In `--warn`, nicht in Petrol — es
|
||
ist keine Einladung, sondern eine Sache, die geklärt werden muss. */
|
||
.live-conflict{
|
||
position: fixed; inset: 0 0 auto 0; z-index: 1200;
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
gap: 16px; padding: 10px 16px;
|
||
background: var(--warn, #B45309); color: #fff;
|
||
font: 500 14px/1.4 'IBM Plex Sans', system-ui, sans-serif;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,.25);
|
||
}
|
||
.live-conflict-actions{ display: flex; gap: 8px; flex: 0 0 auto; }
|
||
.live-conflict button{
|
||
font: 500 13px/1 'IBM Plex Sans', system-ui, sans-serif;
|
||
padding: 6px 10px; border-radius: 4px; cursor: pointer;
|
||
border: 1px solid #fff; background: transparent; color: #fff;
|
||
}
|
||
/* „Eigene durchsetzen" ist die Antwort, die nichts von der eigenen Arbeit
|
||
kostet - sie steht deshalb gefüllt da, ohne die andere zu verstecken. */
|
||
.live-conflict button.mine{ background: #fff; color: var(--warn, #B45309); border-color: #fff; }
|
||
.live-conflict button:focus-visible{ outline: 2px solid #fff; outline-offset: 2px; }
|
||
@media print{ .live-conflict{ display: none; } }
|
||
|
||
/* Server-Dokumente im Wähler: der Host neben dem Namen (D76). Ein
|
||
hochgeladenes Dokument heißt wie das lokale, aus dem es entstand — ohne
|
||
diesen Zusatz stünden zwei gleiche Einträge da. Zurückgenommen, denn der
|
||
Name bleibt die Hauptaussage. */
|
||
.docitem-where{
|
||
margin-left: auto; padding-left: 10px;
|
||
font-size: .82em; color: var(--muted); white-space: nowrap;
|
||
}
|
||
.docpick[aria-checked="true"] .docitem-where{ color: inherit; opacity: .75; }
|
||
|
||
/* ---------- Zwei-Fenster-Wächter (D89) ----------
|
||
Modaler Dialog, solange ein zweites lebendes Werkbaum-Fenster desselben
|
||
Browsers nachweisbar ist (Herzschlag); schließt sich von selbst, sobald
|
||
das andere Fenster zu ist. Über allem (Konflikt-Band liegt bei 1200). */
|
||
.tabmodal-overlay{
|
||
position:fixed;inset:0;z-index:1300;
|
||
background:rgba(36,52,71,.55);
|
||
display:flex;align-items:center;justify-content:center;padding:20px;
|
||
}
|
||
.tabmodal{
|
||
background:var(--paper);border-radius:10px;max-width:440px;
|
||
padding:22px 24px;box-shadow:0 12px 40px rgba(0,0,0,.35);
|
||
}
|
||
.tabmodal h2{margin:0 0 10px;font-size:1.05rem;color:var(--line)}
|
||
.tabmodal p{margin:0 0 14px;line-height:1.45}
|
||
.tabmodal-force{
|
||
background:none;border:none;padding:0;cursor:pointer;
|
||
color:var(--muted);text-decoration:underline;font:inherit;font-size:.85em;
|
||
}
|
||
@media print{ .tabmodal-overlay{ display:none; } }
|
||
|
||
/* ---------- Taiga-Anlege- und Anmeldedialog (D91) ----------
|
||
Im tabmodal-Idiom (Overlay + Karte); nur die Innereien sind eigen. */
|
||
.taigadlg .taiga-subject{margin:0 0 12px;font-weight:600}
|
||
.taigadlg .taiga-lab{display:block;margin:0 0 10px}
|
||
.taigadlg .taiga-lab span{display:block;font-size:.78rem;color:var(--muted);margin-bottom:3px}
|
||
.taigadlg .taiga-lab input,.taigadlg .taiga-lab select{
|
||
width:100%;box-sizing:border-box;padding:7px 9px;font:inherit;
|
||
border:1px solid rgba(36,52,71,.3);border-radius:7px;background:var(--paper);color:var(--ink);
|
||
}
|
||
.taigadlg .taiga-pick{margin:12px 0 6px;font-size:.82rem;color:var(--muted)}
|
||
.taigadlg .taiga-cands{max-height:11rem;overflow-y:auto;display:flex;flex-direction:column;gap:4px}
|
||
.taigadlg .taiga-cand{display:flex;align-items:center;gap:6px;font-size:.88rem}
|
||
.taigadlg .taiga-err{color:var(--warn);font-size:.85rem;margin:10px 0 0}
|
||
.taigadlg .taiga-btns{display:flex;justify-content:flex-end;gap:10px;margin-top:16px}
|
||
.taigadlg .taiga-ok,.taigadlg .taiga-cancel{
|
||
padding:7px 14px;border-radius:7px;font:inherit;cursor:pointer;
|
||
}
|
||
.taigadlg .taiga-ok{border:1px solid var(--or);background:var(--or);color:#fff;font-weight:500}
|
||
.taigadlg .taiga-ok:disabled,.taigadlg .taiga-cancel:disabled{opacity:.5;cursor:default}
|
||
.taigadlg .taiga-cancel{border:1px solid rgba(36,52,71,.3);background:none;color:var(--ink)}
|
||
|
||
/* Abschnitts-Kopf „Lokale Sicherungen" im Uhr-Menü (D89) */
|
||
.snaphead{
|
||
padding:8px 12px 4px;margin-top:4px;
|
||
font-size:.72rem;letter-spacing:.06em;text-transform:uppercase;
|
||
color:var(--muted);border-top:1px solid rgba(107,122,140,.25);
|
||
}
|