Zwei Lücken, die beim Arbeiten am Text auffallen: **Zeilennummern (D33).** Die Warnungen nennen Zeilennummern („Zeile 12: …"), das Textfeld zeigte keine — man musste abzählen. Der Streifen links vom Textfeld hebt zusätzlich die Cursor-Zeile und Zeilen mit Warnung hervor. Die Zahlen stehen auf **gemessenen** Höhen (Spiegel-`div` aus D25, ein Marker je Zeile), nicht auf „Zeilenhöhe × n": Der Text bricht weich um, eine lange Zeile belegt mehrere Bildzeilen und behält doch eine Nummer. Bei 375 px Breite brechen 134 der 143 Zeilen um — gerechnet liefe der Streifen sofort davon. Dabei fiel ein älterer Fehler im Spiegel auf: `width = src.clientWidth` bei `content-box` machte ihn um genau die 32 px Innenabstand zu breit, er brach später um als das Textfeld. Mit `box-sizing:border-box` stimmen die Umbrüche nachweislich überein (170 Bildzeilen beiderseits) — das behebt nebenbei die Zielgenauigkeit des Sprungs zu langen Zeilen (D25). **Alt+Klick im Textfeld (Nachtrag zu D25).** Die Rückrichtung lief bisher allein über die Cursor-Zeile — und die scrollt bewusst nur `nearest` und nur beim Zeilenwechsel, sonst ruckelte das Diagramm beim Tippen. Als „zeig mir das" taugt sie damit nicht. Alt+Klick im Text (Tastatur Alt+Enter) zentriert den Knoten jetzt und gibt ihm den Fokus: derselbe Modifier in beide Richtungen, und mit Fokus wird daraus ein vollständiger Hin- und Rückweg. Die Legenden-Zeile nennt beide Richtungen (kein zehnter i18n-Schlüssel), 9 Sprachen nachgezogen. SPEC §9 ergänzt, D33 angelegt, Plan-Dokument und frontend/CLAUDE.md nachgeführt. Co-Authored-By: Claude <noreply@anthropic.com>
1031 lines
55 KiB
CSS
1031 lines
55 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}
|
||
/* Pad-Rahmen | interner Spiegel (D31). Eigener Bereich, damit die
|
||
Legenden-Aufteilung (D26) unangetastet bleibt. Nur in der Ansicht „beide"
|
||
bekommt der Spiegel eine feste Größe (`--pcol`/`--prow`) — sonst füllt er
|
||
wie bisher den Bereich. Die 85 %-Grenze steht zusätzlich hier im CSS, weil
|
||
der gespeicherte Wert ein px-Wert ist und der Bereich später schrumpfen
|
||
kann (dieselbe Falle wie bei der Legende, D26). */
|
||
.src-area{display:flex;align-items:stretch;flex:1 1 auto;min-width:0;min-height:0}
|
||
.padframe{flex:1 1 auto;min-width:0;min-height:0;border:0;background:var(--card)}
|
||
.padframe[hidden]{display:none}
|
||
/* Zeilennummern + Textfeld sind eine Spalte (`.src-wrap`). Der Pad-Splitter
|
||
teilt gegen diese Spalte — nicht gegen das nackte Textfeld, sonst stünde der
|
||
Zahlenstreifen außerhalb der geteilten Breite. */
|
||
.src-wrap{display:flex;align-items:stretch;flex:1 1 auto;min-width:0;min-height:0}
|
||
.src-area.pv-both>.src-wrap{
|
||
flex:0 0 var(--pcol,300px);max-width:85%;
|
||
border-left:1px dashed rgba(36,52,71,.18);
|
||
}
|
||
.src-area.pv-pad>.src-wrap{display:none}
|
||
.side .src-area,body.mobile .src-area{flex-direction:column}
|
||
.side .src-area.pv-both>.src-wrap,body.mobile .src-area.pv-both>.src-wrap{
|
||
flex:0 0 var(--prow,180px);max-width:none;max-height:85%;
|
||
border-left:0;border-top:1px dashed rgba(36,52,71,.18);
|
||
}
|
||
/* 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. */
|
||
.freshbtn{position:relative;color:#A16207;border-color:#FACC15;background:#FEFCE8}
|
||
.freshbtn:hover{color:#854D0E;border-color:#EAB308}
|
||
.freshbtn[hidden]{display:none}
|
||
.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}
|
||
/* Splitter Pad|Spiegel — gleiches Idiom wie der Legenden-Splitter. */
|
||
.pad-gutter{
|
||
flex:0 0 10px;align-self:stretch;
|
||
cursor:col-resize;touch-action:none;
|
||
display:flex;align-items:center;justify-content:center;
|
||
}
|
||
.pad-gutter[hidden]{display:none}
|
||
.pad-gutter::before{content:'';width:3px;height:38px;border-radius:2px;background:rgba(36,52,71,.18)}
|
||
.pad-gutter:hover::before,.pad-gutter.dragging::before{background:var(--or)}
|
||
.side .pad-gutter,body.mobile .pad-gutter{cursor:row-resize}
|
||
.side .pad-gutter::before,body.mobile .pad-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;
|
||
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}
|
||
/* „im Pad bearbeiten" (D31) — nur bei einem ?etherpad=-Dokument sichtbar.
|
||
Das [hidden] braucht die eigene Regel, weil .copybtn ein display setzt. */
|
||
.padlink{text-decoration:none}
|
||
.padlink[hidden],.padrefresh[hidden],.padview[hidden]{display:none}
|
||
/* Der Ansichts-Wähler zeigt seinen Zustand als Position im Rahmen: geteilt,
|
||
links gefüllt (Pad) oder rechts gefüllt (Spiegel). Zwei Klassen, damit die
|
||
Regel die allgemeinere `.copybtn svg{display:block}` sicher schlägt. */
|
||
.copybtn.padview .ic-both,
|
||
.copybtn.padview .ic-pad,
|
||
.copybtn.padview .ic-text{display:none}
|
||
.copybtn.padview[data-view="both"] .ic-both,
|
||
.copybtn.padview[data-view="pad"] .ic-pad,
|
||
.copybtn.padview[data-view="text"] .ic-text{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 */
|
||
/* 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}
|
||
.docitem.editing{cursor:default}
|
||
.docrename{
|
||
flex:1 1 auto;min-width:0;
|
||
border:1px solid var(--or);border-radius:6px;
|
||
padding:5px 7px;color:var(--ink);background:var(--card);
|
||
font-family:inherit;font-size:.86rem;outline:none;
|
||
}
|
||
.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;
|
||
}
|
||
/* Pad-Dokument (D31): hier nur lesen, geschrieben wird im Pad. Abgesetzt über
|
||
den Hintergrund, damit der Schreibschutz nicht als hängendes Textfeld
|
||
erscheint — die Schrift bleibt aber Tinte: hier wird gelesen, der Plantext
|
||
ist der Hauptinhalt und kein ausgegrautes Formularfeld. */
|
||
textarea.readonly{background:#F1F4F7;cursor:default}
|
||
/* Zeilennummern (D33). Die Warnungen nennen Zeilennummern — ohne Streifen
|
||
muss man sie im Textfeld abzählen. Weich umbrochene Zeilen belegen mehrere
|
||
Bildzeilen, deshalb steht jede Zahl auf einer **gemessenen** Höhe
|
||
(`renderLineNos()` in app.js), nicht auf „Zeilenhöhe × n". 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;
|
||
}
|
||
.src-wrap.readonly .lineno{background:#F1F4F7}
|
||
.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}
|
||
.lineno-inner span.warn{opacity:1;color:var(--warn);font-weight:600}
|
||
.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}
|
||
.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)}
|
||
/* 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. */
|
||
/* Die Fokusmarke `!!!` (SPEC §1) trägt bewusst DIESELBE Hervorhebung: Es gibt
|
||
nur einen Begriff „hier schauen", und ein dritter Ring neben Fokusring,
|
||
Strahlenkranz und Cursor-Ring wäre ein Zeichen zu viel. Der Unterschied
|
||
steht im `aria-label`. */
|
||
#out .node.current{box-shadow:0 0 0 2px var(--card),0 0 0 4px var(--ink)}
|
||
/* 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). */
|
||
#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)}
|
||
/* 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)}
|
||
/* 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}
|
||
/* 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;
|
||
}
|
||
.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)}
|
||
/* „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}
|
||
/* 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}
|
||
/* 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: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;
|
||
}
|
||
|
||
/* --- 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;
|
||
left:-5px;top:50%;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:50%;transform:translateY(-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{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;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}
|
||
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}
|
||
/* Editierhilfe bzw. Zuruf, nicht drucken (D25, Fokusmarke SPEC §1) */
|
||
.node.current,.node.focusmark{box-shadow:none!important}
|
||
.node.fresh{box-shadow:none!important} /* „neu seit dem letzten Besuch" ist persönlich (D28) */
|
||
@page{margin:12mm}
|
||
}
|