Der Editor kann jetzt mehrere Notationstexte halten und zwischen ihnen
umschalten. Der Name des aktiven Dokuments ersetzt die feste Beschriftung
„Struktur (Text)" und öffnet als Dropdown den Wähler mit Wechseln, Neu,
Umbenennen und Löschen.
- Datenmodell (noch kein Backend): Dokumente als [{id,name,text}] in
localStorage (werkbaum-docs), aktives per id in werkbaum-active; der
aktive Text bleibt zusätzlich in werkbaum-src gespiegelt.
- Migration verlustfrei: fehlt werkbaum-docs, wird der bestehende Einzeltext
(oder INITIAL) in ein Dokument gepackt. Reset und Löschen des letzten
Dokuments säen das Beispiel neu.
- Jedes Dokument ist nur Text + Name (kein Strukturformat, D14) —
vorwärtskompatibel zum geplanten Backend (D13). Ansichts-State (Modus,
Zoom, Aufteilung) bleibt global.
- i18n in allen 9 Sprachen; Doc-Namen bleiben Nutzerdaten (nicht übersetzt).
- Dokumentiert als ADR D22 (DECISIONS.md) + frontend/CLAUDE.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
745 lines
38 KiB
CSS
745 lines
38 KiB
CSS
/*! IBM Plex Sans & IBM Plex Mono — Copyright 2019 IBM Corp.
|
|
Licensed under the SIL Open Font License 1.1 — https://scripts.sil.org/OFL
|
|
(kein Reserved Font Name deklariert). Volltext: frontend/src/fonts/OFL.txt */
|
|
/* Lokal eingebettet (self-hosted) statt von Google Fonts geladen — kein
|
|
externer Request, keine IP-Übertragung an Dritte (Datenschutz, D20). Nur die
|
|
genutzten Schnitte (Sans 400/500/600, Mono 400/500), Subsets latin +
|
|
latin-ext (decken DE/EN/ES/FR/PL); andere Skripte fallen ohnehin auf
|
|
system-ui zurück. Der Build inlint die .woff2 als data:-URI (D19). */
|
|
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:400;font-display:swap;
|
|
src:url(./fonts/ibm-plex-sans-latin-400-normal.woff2) format('woff2');
|
|
unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
|
|
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:400;font-display:swap;
|
|
src:url(./fonts/ibm-plex-sans-latin-ext-400-normal.woff2) format('woff2');
|
|
unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
|
|
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:500;font-display:swap;
|
|
src:url(./fonts/ibm-plex-sans-latin-500-normal.woff2) format('woff2');
|
|
unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
|
|
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:500;font-display:swap;
|
|
src:url(./fonts/ibm-plex-sans-latin-ext-500-normal.woff2) format('woff2');
|
|
unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
|
|
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:600;font-display:swap;
|
|
src:url(./fonts/ibm-plex-sans-latin-600-normal.woff2) format('woff2');
|
|
unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
|
|
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:600;font-display:swap;
|
|
src:url(./fonts/ibm-plex-sans-latin-ext-600-normal.woff2) format('woff2');
|
|
unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
|
|
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:400;font-display:swap;
|
|
src:url(./fonts/ibm-plex-mono-latin-400-normal.woff2) format('woff2');
|
|
unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
|
|
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:400;font-display:swap;
|
|
src:url(./fonts/ibm-plex-mono-latin-ext-400-normal.woff2) format('woff2');
|
|
unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
|
|
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:500;font-display:swap;
|
|
src:url(./fonts/ibm-plex-mono-latin-500-normal.woff2) format('woff2');
|
|
unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
|
|
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:500;font-display:swap;
|
|
src:url(./fonts/ibm-plex-mono-latin-ext-500-normal.woff2) format('woff2');
|
|
unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
|
|
|
|
:root{
|
|
--paper:#F3F6F9;
|
|
--grid:rgba(36,52,71,.055);
|
|
--ink:#243447;
|
|
--line:#41556E;
|
|
--card:#FFFFFF;
|
|
--muted:#6B7A8C;
|
|
--or:#0F766E;
|
|
--warn:#B45309;
|
|
--r:8px;
|
|
/* Exakte sichtbare Höhe; JS überschreibt sie auf documentElement aus
|
|
window.visualViewport.height (setViewportHeight). Default = Fallback. */
|
|
--app-height:100dvh;
|
|
}
|
|
*{box-sizing:border-box;margin:0;padding:0}
|
|
body{
|
|
font-family:'IBM Plex Sans',system-ui,sans-serif;
|
|
color:var(--ink);
|
|
background:
|
|
repeating-linear-gradient(0deg,transparent 0 23px,var(--grid) 23px 24px),
|
|
repeating-linear-gradient(90deg,transparent 0 23px,var(--grid) 23px 24px),
|
|
var(--paper);
|
|
/* Mobile Viewport-Höhe: 100vh ist die *große* Höhe (inkl. Fläche hinter der
|
|
unteren Browserleiste) — Footer/Editor-Titelzeile rutschen dahinter. Wo der
|
|
Browser die dvh-Einheit kennt, übersteuert der @supports-Block unten auf
|
|
--app-height (von JS aus window.visualViewport gesetzt, Default 100dvh) —
|
|
das folgt der wirklich sichtbaren Fläche (auch bei Overlay-Leisten wie in
|
|
Brave). Bewusst per @supports getrennt statt gestapelter height-Zeilen,
|
|
damit keine „height wird überschrieben"-Warnung entsteht. */
|
|
height:100vh;
|
|
overflow:hidden;
|
|
display:flex;flex-direction:column;
|
|
padding:24px 20px 12px;
|
|
}
|
|
/* dvh-fähige Browser: exakte sichtbare Höhe statt 100vh (siehe body oben). */
|
|
@supports (height:100dvh){
|
|
body{height:var(--app-height,100dvh)}
|
|
}
|
|
header{max-width:1200px;width:100%;margin:0 auto 16px;flex:0 0 auto}
|
|
.header-row{display:flex;align-items:center;justify-content:space-between;gap:16px}
|
|
.header-tools{display:flex;align-items:center;gap:12px}
|
|
.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)}
|
|
/* 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}
|
|
/* 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}
|
|
.sub-short{display:none} /* Kurzfassung nur auf kleinem Bildschirm */
|
|
.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 sie als
|
|
Inline-Style: --col/--drow per Splitter, --pmin-d/-e per syncPanelMins).
|
|
Werte = die bisherigen var()-Fallbacks; die JS-Erkennung „hat der Nutzer
|
|
gezogen?" liest app.style (Inline) und bleibt davon unberührt. */
|
|
--col:360px;--drow:60%;--pmin-d:49px;--pmin-e:44px;
|
|
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}
|
|
.agenda{
|
|
flex:0 0 auto;
|
|
border-left:1px dashed rgba(36,52,71,.18);
|
|
display:flex;flex-direction:column;
|
|
}
|
|
.agenda[open]{width:300px}
|
|
.agenda>summary{
|
|
list-style:none;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::-webkit-details-marker{display:none}
|
|
.agenda>summary::before{content:'▸';display:inline-block;transition:transform .15s}
|
|
.agenda[open]>summary::before{transform:rotate(90deg)}
|
|
.agenda{min-height:0}
|
|
.agenda[open]{overflow:hidden}
|
|
.agenda .hint{border-top:0;flex:1 1 auto;min-height:0;overflow:auto}
|
|
.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}
|
|
|
|
.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;
|
|
display:flex;justify-content:space-between;align-items:center;gap:14px;
|
|
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}
|
|
.copybtn.done .ic-copy{display:none}
|
|
.copybtn.done .ic-done{display:block}
|
|
.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 */
|
|
/* Dokument-Wähler: der Name des aktiven Dokuments in der Editor-Titelzeile ist
|
|
zugleich der Auslöser eines Dropdowns zum Wechseln/Anlegen/Umbenennen/Löschen
|
|
(localStorage-Dokumente, noch kein Backend). */
|
|
.doctrigger{
|
|
display:inline-flex;align-items:center;gap:5px;cursor:pointer;
|
|
margin-right:auto;max-width:60%;color:var(--muted);
|
|
padding:2px 6px;margin-left:-6px;border-radius:6px;
|
|
text-transform:none;letter-spacing:0;font-family:'IBM Plex Mono',monospace;
|
|
}
|
|
.doctrigger:hover,.doctrigger[aria-expanded="true"]{color:var(--or)}
|
|
.doctrigger:focus-visible{outline:2px solid var(--or);outline-offset:2px}
|
|
.docname{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}
|
|
.doc-caret{width:13px;height:13px;flex:0 0 auto;opacity:.7}
|
|
.panel.collapsed .doc-caret{display:none}
|
|
.docmenu{
|
|
position:absolute;top:calc(100% + 4px);left:12px;z-index:50;min-width:210px;
|
|
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;
|
|
}
|
|
.docmenu[hidden]{display:none}
|
|
.docmenu-list{display:flex;flex-direction:column;gap:2px;max-height:260px;overflow:auto}
|
|
.docitem{
|
|
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;
|
|
}
|
|
.docitem:hover{background:rgba(15,118,110,.08)}
|
|
.docitem[aria-checked="true"]{color:var(--or);font-weight:600}
|
|
.docitem .doccheck{flex:0 0 auto;width:14px;text-align:center;opacity:0}
|
|
.docitem[aria-checked="true"] .doccheck{opacity:1}
|
|
.docmenu-actions{
|
|
display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;padding-top:6px;
|
|
border-top:1px solid rgba(36,52,71,.12);
|
|
}
|
|
.docact{
|
|
flex:1 1 auto;border:1px solid rgba(36,52,71,.2);border-radius:7px;
|
|
background:var(--card);color:var(--muted);cursor:pointer;
|
|
padding:5px 8px;font-family:inherit;font-size:.78rem;white-space:nowrap;
|
|
}
|
|
.docact:hover{border-color:var(--or);color:var(--or)}
|
|
.docdel:hover{border-color:#D897A8;color:#B4415E}
|
|
.togglebtn[aria-pressed="true"]{background:var(--or);color:#fff;border-color:var(--or)}
|
|
.togglebtn[aria-pressed="true"]:hover{color:#fff}
|
|
textarea{
|
|
display:block;width:100%;min-height:0;
|
|
border:0;outline:none;resize:none;
|
|
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;
|
|
}
|
|
.hint{
|
|
padding:12px 16px;border-top:1px dashed rgba(36,52,71,.18);
|
|
font-size:.8rem;color:var(--muted);line-height:1.7;
|
|
}
|
|
.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}
|
|
.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;
|
|
white-space:nowrap;
|
|
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)}
|
|
/* 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)}
|
|
.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;
|
|
}
|
|
.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);
|
|
}
|
|
.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)}
|
|
/* implizit als M angenommene Größe: invertiertes Badge (weiß statt petrol) */
|
|
.size.implicit{background:var(--card);color:var(--or);border-color:var(--or)}
|
|
/* 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}
|
|
.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}
|
|
|
|
/* --- 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}
|
|
ul.and>li::before,
|
|
ul.and>li::after{
|
|
content:'';position:absolute;top:0;right:50%;
|
|
border-top:2px solid var(--line);width:50%;height:16px;
|
|
}
|
|
ul.and>li::after{right:auto;left:50%;border-left:2px solid var(--line)}
|
|
ul.and>li:only-child{padding-top:14px}
|
|
ul.and>li:only-child::before,ul.and>li:only-child::after{border-top:none}
|
|
ul.and>li:only-child::after{border-left:2px solid var(--line);height:14px}
|
|
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}
|
|
|
|
/* --- 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);
|
|
}
|
|
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;
|
|
}
|
|
|
|
/* ---------- 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{flex-direction:column;align-items:flex-start;padding-top:14px}
|
|
.tree.vertical ul.and::before, .tree.kompakt ul.and::before{left:9px;height:14px}
|
|
.tree.vertical ul.and>li, .tree.kompakt ul.and>li{padding:5px 0 5px 30px}
|
|
.tree.vertical ul.and>li::before, .tree.kompakt 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.vertical ul.and>li::after, .tree.kompakt 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.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}
|
|
.tree.vertical li.has-and>ul.and{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:9px;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)}
|
|
.tree.vertical li.has-and>ul.and>li:only-child::after{border:0}
|
|
.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. Standardmäßig ist ein
|
|
Bereich groß, der andere schrumpft auf seine Titelzeile und dient als
|
|
Umschalter (Tippen bringt ihn nach vorn). Die Min/Normal/Max-Buttons
|
|
entfallen; geteilt wird weiterhin über den **Splitter**, der sichtbar
|
|
bleibt. Das gestapelte Layout inkl. Gutter liefert bereits die
|
|
820-px-Regel weiter oben. Die Legende bekommt einen eigenen Umschalter
|
|
statt der dauerhaften „AGENDA“-Zeile. */
|
|
body.mobile .winbtns{display:none}
|
|
/* Mobil: freie Aufteilung per Splitter — beide Titelzeilen bleiben IMMER
|
|
stehen (Zeilen-Minimum --pmin ≈ Titelzeilenhöhe). Kein diskretes
|
|
Minimieren/Snap; ein zusammengeschobenes Panel schrumpft nur bis auf seine
|
|
volle Titelzeile (Körper flext auf 0, Panel ist overflow:hidden). Gilt auch
|
|
für den Seitenmodus, der auf Mobil ohnehin gestapelt ist. Der Drag setzt
|
|
--drow (JS klemmt), das Tippen auf eine Titelzeile maximiert das Panel. */
|
|
body.mobile .app,
|
|
body.mobile .app.side{
|
|
grid-template-columns:minmax(0,1fr);
|
|
grid-template-rows:minmax(var(--pmin-d,49px),var(--drow,60%)) 14px minmax(var(--pmin-e,44px),1fr);
|
|
grid-template-areas:"diagram" "gutter" "editor";
|
|
}
|
|
/* Untertitel: Kurzfassung, garantiert einzeilig (Ellipsis als Sicherung) */
|
|
body.mobile .sub-full{display:none}
|
|
body.mobile .sub-short{display:inline}
|
|
body.mobile header p{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
/* 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}
|
|
/* 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}
|
|
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{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}
|
|
@page{margin:12mm}
|
|
}
|