Files
werkbaum/frontend/src/style.css
T
mhoennigandClaude Opus 5 a223e6af57 fix(taiga): Knoten-Fenster-Knöpfe in einer Zeile — tipwide, Öffnen-Knopf in der Aktions-Zeile (D91-Nachtrag 12)
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>
2026-08-28 13:01:25 +02:00

1751 lines
98 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*! 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);
}