/*! IBM Plex Sans & IBM Plex Mono — Copyright 2019 IBM Corp. Licensed under the SIL Open Font License 1.1 — https://scripts.sil.org/OFL (kein Reserved Font Name deklariert). Volltext: frontend/src/fonts/OFL.txt */ /* Lokal eingebettet (self-hosted) statt von Google Fonts geladen — kein externer Request, keine IP-Übertragung an Dritte (Datenschutz, D20). Nur die genutzten Schnitte (Sans 400/500/600, Mono 400/500), Subsets latin + latin-ext (decken DE/EN/ES/FR/PL); andere Skripte fallen ohnehin auf system-ui zurück. Der Build inlint die .woff2 als data:-URI (D19). */ @font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:400;font-display:swap; src:url(./fonts/ibm-plex-sans-latin-400-normal.woff2) format('woff2'); unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD} @font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:400;font-display:swap; src:url(./fonts/ibm-plex-sans-latin-ext-400-normal.woff2) format('woff2'); unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF} @font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:500;font-display:swap; src:url(./fonts/ibm-plex-sans-latin-500-normal.woff2) format('woff2'); unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD} @font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:500;font-display:swap; src:url(./fonts/ibm-plex-sans-latin-ext-500-normal.woff2) format('woff2'); unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF} @font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:600;font-display:swap; src:url(./fonts/ibm-plex-sans-latin-600-normal.woff2) format('woff2'); unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD} @font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:600;font-display:swap; src:url(./fonts/ibm-plex-sans-latin-ext-600-normal.woff2) format('woff2'); unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF} @font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:400;font-display:swap; src:url(./fonts/ibm-plex-mono-latin-400-normal.woff2) format('woff2'); unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD} @font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:400;font-display:swap; src:url(./fonts/ibm-plex-mono-latin-ext-400-normal.woff2) format('woff2'); unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF} @font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:500;font-display:swap; src:url(./fonts/ibm-plex-mono-latin-500-normal.woff2) format('woff2'); unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD} @font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:500;font-display:swap; src:url(./fonts/ibm-plex-mono-latin-ext-500-normal.woff2) format('woff2'); unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF} :root{ --paper:#F3F6F9; --grid:rgba(36,52,71,.055); --ink:#243447; --line:#41556E; --card:#FFFFFF; --muted:#6B7A8C; --or:#0F766E; --warn:#B45309; --r:8px; /* Exakte sichtbare Höhe; JS überschreibt sie auf documentElement aus window.visualViewport.height (setViewportHeight). Default = Fallback. */ --app-height:100dvh; } *{box-sizing:border-box;margin:0;padding:0} body{ font-family:'IBM Plex Sans',system-ui,sans-serif; color:var(--ink); background: repeating-linear-gradient(0deg,transparent 0 23px,var(--grid) 23px 24px), repeating-linear-gradient(90deg,transparent 0 23px,var(--grid) 23px 24px), var(--paper); /* Mobile Viewport-Höhe: 100vh ist die *große* Höhe (inkl. Fläche hinter der unteren Browserleiste) — Footer/Editor-Titelzeile rutschen dahinter. Wo der Browser die dvh-Einheit kennt, übersteuert der @supports-Block unten auf --app-height (von JS aus window.visualViewport gesetzt, Default 100dvh) — das folgt der wirklich sichtbaren Fläche (auch bei Overlay-Leisten wie in Brave). Bewusst per @supports getrennt statt gestapelter height-Zeilen, damit keine „height wird überschrieben"-Warnung entsteht. */ height:100vh; overflow:hidden; display:flex;flex-direction:column; padding:24px 20px 12px; } /* dvh-fähige Browser: exakte sichtbare Höhe statt 100vh (siehe body oben). */ @supports (height:100dvh){ body{height:var(--app-height,100dvh)} } header{max-width:1200px;width:100%;margin:0 auto 16px;flex:0 0 auto} .header-row{display:flex;align-items:center;justify-content:space-between;gap:16px} .header-tools{display:flex;align-items:center;gap:12px} /* Marke + aktives Dokument als Brotkrume (D81): „Werkbaum › name". Die Gruppe darf schrumpfen (min-width:0), damit der Chip lange Namen mit Ellipse kürzt, statt die Werkzeuge rechts zu verdrängen. */ .brandgroup{display:flex;align-items:center;gap:10px;min-width:0;flex:1 1 auto} .crumb-sep{color:var(--muted);opacity:.6;font-size:1.05rem;flex:0 0 auto;user-select:none} .docwrap{position:relative;display:inline-flex;align-items:center;min-width:0} /* Dokumentart hinter dem Chip (D90): grau und klein — Auskunft, kein Knopf. flex:0 0 auto, damit bei langen Namen der Name die Ellipse bekommt und die Art stehen bleibt (sie ist die Information, die vor Verwechslung schützt). */ .dockind{ flex:0 0 auto;margin-left:8px;color:var(--muted); font-size:.72rem;white-space:nowrap; } /* Verbindungs-Status am Chip (D90-Nachtrag): getrennt/ungesendet in Warnfarbe */ .dockind.warn{color:var(--warn);font-weight:600} /* Der Auslöser sieht jetzt nach Menü aus: gerahmter Chip mit Pfeil — die frühere nackte Beschriftung in der Panel-Zeile war als Knopf nicht zu erkennen (der Anlass von D81). */ .doctrigger{ display:inline-flex;align-items:center;gap:6px;cursor:pointer;min-width:0; border:1px solid rgba(36,52,71,.2);border-radius:8px;background:var(--card); padding:4px 10px;color:var(--ink); font-family:'IBM Plex Mono',monospace;font-size:.78rem;letter-spacing:0; } .doctrigger:hover,.doctrigger[aria-expanded="true"]{border-color:var(--or);color:var(--or)} .doctrigger:focus-visible{outline:2px solid var(--or);outline-offset:2px} /* Rückmeldung von „Teilen" (putOnServer, D76/D81): Petrol-Blitz samt Haken am Dokumentnamen — er ist das, was jetzt auf dem Server liegt. */ .doctrigger.done{border-color:var(--or);color:var(--or)} .doctrigger.done::before{content:"✓ ";flex:0 0 auto} .docname{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600;max-width:min(44vw,520px)} .doc-caret{width:13px;height:13px;flex:0 0 auto;opacity:.7} /* Das Dokumenten-Menü hängt am Chip im App-Kopf. Es scrollt als GANZES (max-height), statt nur in der Liste — auf dem Telefon wurde das alte Menü unten abgeschnitten (D81); erwogen waren auf-/zuklappbare Gruppen, das Scrollen ist die zustandslose Antwort. Kein `overflow` am Vorfahren nötig — der App-Kopf ist keiner (D50 gilt hier nicht). */ .docmenu{ position:absolute;top:calc(100% + 6px);left:0;z-index:60; width:min(360px,calc(100vw - 24px));max-height:min(72vh,560px);overflow:auto; background:var(--card);border:1px solid rgba(36,52,71,.15);border-radius:10px; padding:6px;box-shadow:0 8px 24px rgba(36,52,71,.22); font-family:'IBM Plex Sans',sans-serif;font-weight:400;letter-spacing:0; text-align:left;color:var(--ink); } .docmenu[hidden]{display:none} .docmenu-list{display:flex;flex-direction:column;gap:2px} /* Gruppen nach Dokumentart (docKind, D81): Mitgeliefert · Eigene · Quellen. */ .docsec{ margin:8px 4px 2px;padding-top:7px;border-top:1px solid rgba(36,52,71,.1); font-family:'IBM Plex Mono',monospace;font-size:.62rem;letter-spacing:.1em; text-transform:uppercase;color:var(--muted); } .docmenu-list .docsec:first-child{margin-top:2px;padding-top:0;border-top:0} /* Eine Zeile = Wählen-Knopf + Zeilen-Aktionen. Kein Knopf im Knopf (ungültiges HTML) — die Zeile ist ein div, beides sind Geschwister. */ .docitem{display:flex;align-items:center;gap:2px;border-radius:7px} .docpick{ display:flex;align-items:center;gap:8px;flex:1 1 auto;min-width:0; border:0;background:transparent;cursor:pointer;text-align:left; padding:7px 9px;border-radius:7px;color:var(--ink); font-family:inherit;font-size:.86rem; } .docpick:hover{background:rgba(15,118,110,.08)} .docpick[aria-checked="true"]{color:var(--or);font-weight:600} .docpick .doccheck{flex:0 0 auto;width:14px;text-align:center;opacity:0} .docpick[aria-checked="true"] .doccheck{opacity:1} .docitem-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0} .docitem.editing{cursor:default;padding:0 0 0 9px;gap:8px} .docrename{ flex:1 1 auto;min-width:0; border:1px solid var(--or);border-radius:6px; padding:5px 7px;margin:2px 4px 2px 0;color:var(--ink);background:var(--card); font-family:inherit;font-size:.86rem;outline:none; } /* Zeilen-Aktionen immer sichtbar (Touch kennt kein Hover), aber leise. */ .docacts{display:inline-flex;gap:2px;flex:0 0 auto;padding-right:4px} .dociconbtn{ display:grid;place-items:center;width:26px;height:26px;padding:0; border:0;border-radius:6px;background:transparent;color:var(--muted);cursor:pointer; } .dociconbtn svg{width:14px;height:14px;display:block} .dociconbtn:hover{background:rgba(36,52,71,.08);color:var(--ink)} .docdelbtn:hover{background:rgba(216,151,168,.28);color:#B4415E} /* Befehle unten: gewöhnliche Menüzeilen statt eines Knopf-Wusts. */ .docmenu-cmds{ display:flex;flex-direction:column;gap:2px;margin-top:6px;padding-top:6px; border-top:1px solid rgba(36,52,71,.12); } .doccmd{ display:flex;align-items:center;gap:8px;width:100%; border:0;background:transparent;cursor:pointer;text-align:left; padding:7px 9px;border-radius:7px;color:var(--ink); font-family:inherit;font-size:.86rem; } .doccmd:hover{background:rgba(15,118,110,.08)} .doccmd svg{width:14px;height:14px;flex:0 0 auto;color:var(--muted)} .doccmd-glyph{width:14px;text-align:center;color:var(--muted);font-weight:600;flex:0 0 auto} .fsbtn{ display:inline-flex;align-items:center;justify-content:center; width:30px;height:29px;padding:0;cursor:pointer; border:1px solid rgba(36,52,71,.2);border-radius:8px; background:var(--card);color:var(--muted); } .fsbtn svg{display:block} .fsbtn:hover{border-color:var(--or);color:var(--or)} .fsbtn.active{background:var(--or);color:#fff;border-color:var(--or)} /* Neuigkeiten (D58). Der Knopf steht permanent in der Kopfzeile; **bernstein** heißt „da ist etwas, das du noch nicht gesehen hast" — dieselben Töne, die der Strahlenkranz am Knoten trägt (D28), damit Knopf und Knoten dasselbe sagen. Petrol (`.active`) heißt etwas anderes: Es wird gerade ein bestimmter Tag im Diagramm vorgeführt. */ .newswrap{position:relative;display:inline-flex;flex:0 0 auto} .newsbtn{width:auto;min-width:30px;padding:0 6px;gap:2px} .newsbtn.unseen{color:#A16207;border-color:#FACC15;background:#FEFCE8} .newsbtn.unseen:hover{color:#854D0E;border-color:#EAB308} .newsbtn .freshcount[hidden]{display:none} .newsmenu{ position:absolute;top:calc(100% + 6px);right:0;z-index:60; width:min(420px,calc(100vw - 24px));max-height:min(70vh,520px);overflow:auto; background:var(--card);border:1px solid rgba(36,52,71,.15);border-radius:10px; padding:10px 12px;box-shadow:0 8px 24px rgba(36,52,71,.22); text-transform:none;letter-spacing:0;font-family:'IBM Plex Sans',sans-serif; text-align:left;color:var(--ink); } .newsmenu[hidden]{display:none} .newshead{ display:flex;align-items:center;gap:8px; font-size:.72rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted); } .newshead .newsclose{ margin-left:auto;border:0;background:transparent;cursor:pointer; color:var(--muted);font-size:1rem;line-height:1;padding:2px 4px;border-radius:6px; } .newshead .newsclose:hover{color:var(--ink);background:rgba(36,52,71,.07)} /* Fußnote zur Sprache der Notizen (D58-Nachtrag): klein, grau, ohne Rahmen und ohne Fläche — sie soll gelesen werden können, aber hinter dem Inhalt zurückstehen, zu dem sie gehört. */ .newsnote{margin-top:7px;color:var(--muted);font-size:.73rem;line-height:1.45} /* „Seit deinem letzten Besuch" (D28) — die persönliche Hälfte, deshalb bernstein abgesetzt und oben, vor der allgemeinen Chronik. */ .newssince{ display:flex;align-items:center;gap:8px;flex-wrap:wrap; margin-top:8px;padding:8px 10px;border-radius:8px; background:#FEFCE8;border:1px solid #FACC15;color:#854D0E;font-size:.82rem; } .newssince .newsseen{ margin-left:auto;border:1px solid rgba(133,77,14,.35);border-radius:7px; background:transparent;color:inherit;cursor:pointer; padding:3px 9px;font-family:inherit;font-size:.78rem; } .newssince .newsseen:hover{background:rgba(133,77,14,.1)} .newsday{margin-top:10px;padding-top:9px;border-top:1px solid rgba(36,52,71,.12)} .newsdate{font-weight:600;font-size:.84rem} .newslines{margin:5px 0 0;padding-left:17px;font-size:.82rem;line-height:1.55;color:var(--ink)} .newslines li{margin:1px 0} .newslines code{ font-family:'IBM Plex Mono',monospace;font-size:.92em; background:rgba(36,52,71,.07);border-radius:4px;padding:0 3px; } .newsshow{ margin-top:6px;border:1px solid rgba(36,52,71,.2);border-radius:7px; background:var(--card);color:var(--muted);cursor:pointer; padding:4px 9px;font-family:inherit;font-size:.78rem; } .newsshow:hover{border-color:var(--or);color:var(--or)} .newsshow[aria-pressed="true"]{background:var(--or);color:#fff;border-color:var(--or)} .newsempty{margin-top:8px;color:var(--muted);font-size:.82rem} /* Vollbild: Panels nutzen fast die ganze Fensterbreite; es bleibt ein schmaler Rand (halber Panel-Zwischenraum = 7 px), damit es nicht gedrängt wirkt. */ body.fullscreen{padding-left:7px;padding-right:7px} body.fullscreen header, body.fullscreen .app, body.fullscreen .site-footer{max-width:none} h1{font-size:1.25rem;font-weight:600;letter-spacing:-.01em;display:flex;align-items:center;gap:11px} /* Build-Hinweis hinter dem Titel (nur nicht-produktive Builds, siehe app.js) */ .build-badge{margin-left:-.5em;font-size:.58em;line-height:1;vertical-align:middle;cursor:help;user-select:none;text-decoration:none} header p{margin-top:4px;color:var(--muted);font-size:.9rem} .langsel{ display:inline-flex;flex:0 0 auto; border:1px solid rgba(36,52,71,.2);border-radius:999px;overflow:hidden; } .langsel button{ border:0;background:var(--card);cursor:pointer;color:var(--muted); font-family:'IBM Plex Mono',monospace;font-size:.72rem;letter-spacing:.04em; padding:5px 10px; } .langsel button + button{border-left:1px solid rgba(36,52,71,.14)} .langsel button:hover:not(.active){color:var(--or)} .langsel button.active{background:var(--or);color:#fff} .langmore{letter-spacing:0;padding:5px 8px;font-weight:600} .langsel .lang-extra{display:none} /* PL/RU/HI erst nach Aufklappen */ .langsel.expanded .lang-extra{display:block} .site-footer{ max-width:1200px;margin:14px auto 0; color:var(--muted);font-size:.78rem;text-align:center; } .site-footer a{color:var(--or);text-decoration:none} .site-footer a:hover{text-decoration:underline} .site-footer .vibe{cursor:help;text-decoration:underline dotted;text-underline-offset:2px} /* Horizontal (Toggle aus): Diagramm oben, Editor unten — je volle Breite. Vertikal (Toggle an, .side): Editor links, Diagramm rechts. Zwischen beiden liegt ein verschiebbarer Gutter (Splitter). */ .app{ /* Defaults der laufzeit-gesetzten Custom-Properties (JS setzt --col/--drow als Inline-Style per Splitter). Werte = die bisherigen var()-Fallbacks; die JS-Erkennung „hat der Nutzer gezogen?" liest app.style (Inline) und bleibt davon unberührt. (--pmin-d/-e hielten auf Mobil die beiden Titelzeilen frei — entfallen mit dem Ein-Bereich-Modell, D17-Nachtrag.) */ --col:360px;--drow:60%; max-width:1200px;width:100%;margin:0 auto; flex:1 1 auto;min-height:0; display:grid;gap:0; grid-template-columns:minmax(0,1fr); grid-template-rows:var(--drow,60%) 14px minmax(0,1fr); grid-template-areas:"diagram" "gutter" "editor"; align-items:stretch; } .app.side{ grid-template-columns:var(--col,360px) 14px minmax(0,1fr); grid-template-rows:minmax(0,1fr); grid-template-areas:"editor gutter diagram"; } .panel.editor{grid-area:editor} .panel.right{grid-area:diagram} .gutter{grid-area:gutter} /* Splitter/Gutter: stapelweise horizontal, seitlich vertikal. */ .gutter{ position:relative;align-self:stretch;justify-self:stretch; height:14px;cursor:row-resize;touch-action:none; } .app.side .gutter{height:auto;width:14px;cursor:col-resize} .gutter::before{ content:'';position:absolute;left:50%;top:50%;transform:translate(-50%,-50%); width:46px;height:4px;border-radius:999px;background:rgba(36,52,71,.16); transition:background .15s; } .app.side .gutter::before{width:4px;height:46px} .gutter:hover::before,.gutter.dragging::before{background:var(--or)} /* Minimieren: Panel auf seine Titelzeile schrumpfen, Körper ausblenden. 'a' = Diagramm minimiert (Editor maximal), 'b' = Editor minimiert. */ .panel.collapsed .editor-body, .panel.collapsed .diagram, .panel.collapsed .warnings{display:none} .panel.collapsed .copybtn, .panel.collapsed .switch, .panel.collapsed .seg{display:none} .panel.collapsed .panel-head{cursor:pointer} .panel.collapsed .panel-head>span{cursor:pointer} /* Gestapelt: kollabiertes Panel = Titelzeile (auto), anderes füllt. */ .app:not(.side).collapse-diagram{grid-template-rows:auto 14px minmax(0,1fr)} .app:not(.side).collapse-editor{grid-template-rows:minmax(0,1fr) 14px auto} /* Seitlich: schmaler vertikaler Streifen mit gedrehter Beschriftung. */ .app.side.collapse-editor{grid-template-columns:auto 14px minmax(0,1fr)} .app.side.collapse-diagram{grid-template-columns:minmax(0,1fr) 14px auto} .app.side .panel.collapsed{align-self:stretch} .app.side .panel.collapsed .panel-head{ flex-direction:column;align-items:center;gap:12px; height:100%;padding:12px 6px;border-bottom:0; } .app.side .panel.collapsed .panel-head>span{ writing-mode:vertical-rl;transform:rotate(180deg); margin:0;white-space:nowrap; } .app.side .panel.collapsed .winbtns{flex-direction:column} /* Schmaler Viewport: Seitenmodus fällt auf gestapelt zurück. */ @media (max-width:820px){ .app.side{ grid-template-columns:minmax(0,1fr); grid-template-rows:var(--drow,60%) 14px minmax(0,1fr); grid-template-areas:"diagram" "gutter" "editor"; } .app.side.collapse-diagram{grid-template-columns:minmax(0,1fr);grid-template-rows:auto 14px minmax(0,1fr)} .app.side.collapse-editor{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr) 14px auto} .app.side .panel.collapsed .panel-head{flex-direction:row;align-items:center;height:auto;padding:10px 16px} .app.side .panel.collapsed .panel-head>span{writing-mode:horizontal-tb;transform:none} .app.side .panel.collapsed .winbtns{flex-direction:row} .app.side .gutter{height:14px;width:auto;cursor:row-resize} .app.side .gutter::before{width:46px;height:4px} } /* Editor-Innenaufbau: Textarea + aufklappbare Agenda. Horizontal nebeneinander, vertikal (.side) untereinander. */ .editor-body{display:flex;align-items:stretch;flex:1 1 auto;min-height:0} .editor-body textarea{flex:1 1 auto;min-width:0;min-height:0} /* Zeilennummern + Textfeld sind eine Spalte (`.src-wrap`, D33). */ .src-wrap{display:flex;align-items:stretch;flex:1 1 auto;min-width:0;min-height:0} /* Legende: bewusst KEIN
mehr. Chrome legt den Details-Inhalt in `::details-content`; dadurch war `.hint` kein Flex-Kind von `.agenda` mehr, `flex:1/min-height:0` liefen ins Leere und der Inhalt wurde geclippt statt scrollbar (D26). Als gewöhnlicher Container ist die Kette wieder dicht. */ .agenda{ flex:0 0 auto; border-left:1px dashed rgba(36,52,71,.18); display:flex;flex-direction:column;min-height:0;min-width:0; } /* Die Aufteilung steht als feste px-Größe. Schrumpft das Panel später (Zug am großen Splitter, Drehung, Tastatur), begrenzt max-* die Legende, damit der Editor nicht auf 0 gedrückt wird — gleiche Schranke wie beim Ziehen. */ .agenda.open{width:var(--hcol,300px);max-width:85%;overflow:hidden} .agenda-summary{ flex:0 0 auto; background:none;border:0;text-align:left; cursor:pointer;white-space:nowrap; padding:10px 16px; font-family:'IBM Plex Mono',monospace; font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted); display:flex;align-items:center;gap:7px; } .agenda-summary::before{content:'▸';display:inline-block;transition:transform .15s} .agenda.open>.agenda-summary::before{transform:rotate(90deg)} .agenda-summary:focus-visible{outline:2px solid var(--or);outline-offset:-2px} .agenda:not(.open) .hint{display:none} .agenda .hint{border-top:0;flex:1 1 auto;min-height:0;overflow:auto} /* Splitter zwischen Editor und Legende — dieselbe Sprache wie der große Splitter, nur schmaler. Ohne offene Legende gibt es nichts zu teilen. */ /* Neuheiten-Knopf im Diagramm-Kopf (D28): Sternchen + Anzahl, nur sichtbar, wenn es etwas gibt. Gelb wie der Strahlenkranz am Knoten. */ /* Die Zahl der neuen Knoten sitzt seit D58 am Neuigkeiten-Knopf in der Kopfzeile (die bernsteinfarbene Fassung steht dort bei `.newsbtn.unseen`). */ .freshcount{ font-family:'IBM Plex Mono',monospace;font-size:.62rem;font-weight:600; margin-left:3px;vertical-align:2px; } .hint-gutter{ flex:0 0 10px;align-self:stretch; cursor:col-resize;touch-action:none; display:flex;align-items:center;justify-content:center; } .hint-gutter[hidden]{display:none} .hint-gutter::before{content:'';width:3px;height:38px;border-radius:2px;background:rgba(36,52,71,.18)} .hint-gutter:hover::before,.hint-gutter.dragging::before{background:var(--or)} .side .editor-body{flex-direction:column;align-items:stretch} .side .agenda{border-left:0;border-top:1px dashed rgba(36,52,71,.18)} .side .agenda.open{width:auto;max-width:none;height:var(--hrow,220px);max-height:85%} .side .hint-gutter{cursor:row-resize} .side .hint-gutter::before{width:38px;height:3px} .panel{ background:var(--card); border:1px solid rgba(15,118,110,.30); /* dezentes Petrol */ border-radius:12px; overflow:hidden; display:flex;flex-direction:column;min-height:0;min-width:0; } .panel-head{ flex:0 0 auto;position:relative; padding:10px 16px; /* gap:10 statt 14 — dieselbe Rechnung wie in D17-Nachtrag 5, nur für den Schreibtisch: Beim zehnten Bedienelement (D56) fehlten 10 px, und zehn Lücken à 14 px machen 140 px Weißraum für 566 px Knöpfe. Verkleinert wird der Zwischenraum, nicht das Ziel; 4 px weniger je Lücke geben die Breite des neuen Knopfes zurück. */ display:flex;justify-content:space-between;align-items:center;gap:10px; font-family:'IBM Plex Mono',monospace; font-size:.7rem;letter-spacing:.08em;text-transform:uppercase; color:var(--muted); border-bottom:1px solid rgba(36,52,71,.1); flex-wrap:wrap; } .panel-head>span{margin-right:auto} .switch{ display:flex;align-items:center;gap:7px;cursor:pointer; text-transform:none;letter-spacing:0; font-family:'IBM Plex Sans',sans-serif;font-size:.78rem;color:var(--muted); } .switch input{position:absolute;opacity:0;width:0;height:0} .switch i{ width:30px;height:17px;border-radius:999px;background:#C6CFD8; position:relative;transition:background .15s;flex:0 0 auto; } .switch i::before{ content:'';position:absolute;top:2px;left:2px;width:13px;height:13px; border-radius:50%;background:#fff;transition:left .15s; box-shadow:0 1px 2px rgba(36,52,71,.25); } .switch input:checked + i{background:var(--or)} .switch input:checked + i::before{left:15px} .seg{ display:inline-flex;align-items:stretch; border:1px solid rgba(36,52,71,.2);border-radius:999px;overflow:hidden; } .seg label{display:flex;cursor:pointer} .seg input{position:absolute;opacity:0;width:0;height:0} .seg span{ display:grid;place-items:center;padding:4px 10px;color:var(--muted); transition:background .15s,color .15s; } .seg span svg{display:block} .seg label + label span{border-left:1px solid rgba(36,52,71,.14)} .seg input:checked + span{background:var(--or);color:#fff} .seg input:focus-visible + span{outline:2px solid var(--or);outline-offset:-2px} .winbtns{display:inline-flex;gap:4px;align-items:center} .winbtn{ width:19px;height:19px;display:grid;place-items:center; border:1px solid rgba(36,52,71,.2);border-radius:5px; background:var(--card);color:var(--muted);cursor:pointer; font-size:.72rem;line-height:1;padding:0; } .winbtn:hover{border-color:var(--or);color:var(--or)} .winbtn.active{background:var(--or);color:#fff;border-color:var(--or)} .copybtn{ color:var(--muted);background:var(--card); border:1px solid rgba(36,52,71,.2);border-radius:6px; padding:3px 6px;cursor:pointer; display:inline-flex;align-items:center;justify-content:center; } .copybtn:hover,.copybtn.done{border-color:var(--or);color:var(--or)} .copybtn svg{display:block;width:15px;height:15px} .copybtn .ic-done{display:none} /* `.ic-main` ist die neutrale Form von `.ic-copy` für Knöpfe, die nichts kopieren, aber dieselbe Erfolgs-Rückmeldung geben (Stand sichern, D54). */ .copybtn.done .ic-copy,.copybtn.done .ic-main{display:none} .copybtn.done .ic-done{display:block} /* Falt-Durchschalter (SPEC §9/D75): vier Icons, sichtbar ist das des NÄCHSTEN Schritts — `data-next` setzt updateFoldBtn(). Zwei Klassen, damit die Regel `.copybtn svg{display:block}` schlägt. */ .copybtn.foldbtn .fc-small, .copybtn.foldbtn .fc-path, .copybtn.foldbtn .fc-closed, .copybtn.foldbtn .fc-open{display:none} .copybtn.foldbtn[data-next="small"] .fc-small, .copybtn.foldbtn[data-next="path"] .fc-path, .copybtn.foldbtn[data-next="closed"] .fc-closed, .copybtn.foldbtn[data-next="open"] .fc-open{display:block} /* Läuft ein Abruf, dreht das Symbol: bei gedrosselter Gegenseite (D31) können das die vollen 20 s sein, und ein Knopf ohne Rückmeldung wirkt kaputt. */ /* Sperre nach einem Abruf (D31): sichtbar gedämpft, aber **klickbar** — der Klick meldet den Grund, ein `disabled` erklärte nichts. */ .padrefresh.cooling{opacity:.45;cursor:not-allowed} .padrefresh.busy{color:var(--or);border-color:var(--or);pointer-events:none} .padrefresh.busy.cooling{opacity:1;cursor:default} .padrefresh.busy svg{animation:padspin 1.1s linear infinite} @keyframes padspin{to{transform:rotate(360deg)}} @media (prefers-reduced-motion:reduce){ .padrefresh.busy svg{animation:none} .padrefresh.busy{opacity:.55} } .dlbtn{gap:3px} .dl-label{font-family:'IBM Plex Mono',monospace;font-size:.62rem;font-weight:600;letter-spacing:.02em} /* Download-Gruppe: Desktop zeigt SVG/PNG inline, der Trigger ist versteckt. Auf Mobil (body.mobile, s. u.) klappt sie als Overlay-Menü auf. */ .dlgroup{display:inline-flex;align-items:center;gap:3px} .dlmenu{display:inline-flex;gap:3px} .dltrigger{display:none} .legendbtn{display:none} /* nur auf kleinem Bildschirm sichtbar */ .panetoggle{display:none} /* ebenso: dort gibt es nur einen Bereich */ /* Zurückschreiben der Faltung (D38-Nachtrag 2): Für die Dauer des SYNCHRONEN Schreibens braucht das Textfeld einen Kasten — ohne ihn tut `execCommand` nichts (gemessen: liefert `false`, obwohl `activeElement` das Feld meldet). Auf kleinem Bildschirm ist der Editor `display:none`, wenn das Diagramm vorn ist. Absolut aus dem Fluss genommen und aus dem Bild geschoben, damit der sichtbare Bereich seine Größe behält; gezeichnet wird davon nichts — derselbe Griff wie `exporting` im Grafikexport. */ body.writing-fold .panel.editor{ display:flex!important; position:absolute!important;left:-10000px!important;top:0!important; width:360px!important;height:400px!important; } /* Stand-Gruppe (D81): Speichern, Kamera, frühere Stände, Neu laden, Teilen — die Funktionen des AKTIVEN Dokuments, links neben der Beschriftung. Die Gruppe trägt das `margin-right:auto` (schiebt die übrigen Knöpfe nach rechts) und ist über `.snapwrap` der Positionsbezug des Snapshot-Menüs. Der Dokument-Wähler selbst steht seit D81 als Brotkrume im App-Kopf. */ .standgroup{display:inline-flex;align-items:center;gap:4px;margin-right:auto} /* Die Beschriftung „Text-Editor" bekommt KEIN auto-Margin (die allgemeine `.panel-head>span`-Regel gäbe ihr eins) — den Platz nimmt die Stand-Gruppe. */ .panel-head>span[data-i18n="editorTitle"]{margin-right:0} /* Bezug des Menüs ist der KNOPF, nicht die Gruppe: An der Gruppe ausgerichtet (`right:0`) begänne es 190 px weiter links und liefe aus dem Panel heraus — nachgemessen bei der ersten Fassung. */ .snapwrap{position:relative;display:inline-flex;flex:0 0 auto} /* Snapshot-Menü (D54): am linken Rand seiner Gruppe ausgerichtet — also unter dem Knopf. */ .snapbtn{flex:0 0 auto} .snapmenu{ position:absolute;top:calc(100% + 6px);left:0;z-index:50;min-width:190px; max-height:280px;overflow:auto; display:flex;flex-direction:column;gap:2px; background:var(--card);border:1px solid rgba(36,52,71,.15);border-radius:10px; padding:6px;box-shadow:0 8px 24px rgba(36,52,71,.22); text-transform:none;letter-spacing:0;font-family:'IBM Plex Sans',sans-serif; } .snapmenu[hidden]{display:none} .snapitem{ display:flex;align-items:baseline;gap:8px;width:100%; border:0;background:transparent;cursor:pointer;text-align:left; padding:6px 9px;border-radius:7px;color:var(--ink); font-family:inherit;font-size:.84rem;white-space:nowrap; } .snapitem:hover{background:rgba(15,118,110,.08)} .snapitem .snapsize{margin-left:auto;color:var(--muted);font-size:.74rem} .snapempty{padding:7px 9px;color:var(--muted);font-size:.8rem} .togglebtn[aria-pressed="true"]{background:var(--or);color:#fff;border-color:var(--or)} .togglebtn[aria-pressed="true"]:hover{color:#fff} /* Kein Zeilenumbruch (D49, `wrap="off"` am Element): Die Einrückung trägt die Hierarchie, und ein weicher Umbruch setzt die Fortsetzung an den linken Rand — die Ebene wäre damit gerade dort nicht mehr abzulesen, wo eine Zeile lang ist. Lange Zeilen bekommen stattdessen einen waagerechten Balken. */ textarea{ display:block;width:100%;min-height:0; border:0;outline:none;resize:none;overflow:auto; padding:14px 16px; font-family:'IBM Plex Mono',monospace; font-size:.85rem;line-height:1.7; color:var(--ink);background:var(--card); tab-size:2; } /* Zeilennummern (D33). Die Warnungen nennen Zeilennummern — ohne Streifen muss man sie im Textfeld abzählen. Jede Zahl steht auf einer **gemessenen** Höhe (`renderLineNos()` in app.js), nicht auf „Zeilenhöhe × n"; seit D49 bricht nichts mehr um, aber die Schriftgröße unterscheidet sich zwischen Telefon und Schreibtisch, und die Messung stimmt in beiden Fällen von selbst. Der Streifen scrollt **nur senkrecht** mit: Er verschiebt sich gegen `src.scrollTop`, den waagerechten Balken des Textfelds macht er nicht mit — die Zahlen bleiben stehen, wo sie hingehören. Typografie muss mit dem Textfeld übereinstimmen, sonst wandern die Zahlen weg. */ .lineno{ position:relative;overflow:hidden;flex:0 0 auto; background:var(--card);border-right:1px solid rgba(36,52,71,.10); font-family:'IBM Plex Mono',monospace;font-size:.85rem;line-height:1.7; color:var(--muted); -webkit-user-select:none;user-select:none; } .lineno-inner{position:absolute;top:0;left:0;right:0} .lineno-inner span{ position:absolute;right:6px; opacity:.5;font-variant-numeric:tabular-nums; } /* Cursor-Zeile und Zeilen mit Warnung heben sich ab — genau die beiden Fälle, für die man im Streifen überhaupt nachsieht. */ .lineno-inner span.cur{opacity:1;color:var(--ink);font-weight:600} /* Die Zahl einer Warn-Zeile trägt die Meldung als Tooltip (D33-Nachtrag). Dafür reicht ihr Kasten über die **ganze** Streifenbreite statt nur über die Ziffern: Ein zwei Zeichen breites Ziel trifft man sonst kaum. Die Ziffern selbst bleiben, wo sie sind (rechtsbündig, 6 px Abstand). */ /* Der Zeiger sagt, WAS hier steht: Pfeil mit **Warndreieck** statt des `?` von `cursor:help` — dasselbe ⚠ wie vor jeder Meldung im Warnungsbereich (D33-Nachtrag 2). Der Pfeil bleibt, sonst zeigte der Zeiger auf nichts; die Spitze liegt auf dem Brennpunkt (1 1). `help` ist der Rückfall, wo ein Bild-Zeiger nicht angenommen wird. */ .lineno-inner span.warn{ opacity:1;color:var(--warn);font-weight:600; left:0;right:0;padding-right:6px;text-align:right; cursor:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32'%3E%3Cpath d='M1 1V17.6L5.3 13.5 8.3 20.2 11.2 18.9 8.2 12.4 13.8 12.2Z' fill='%23243447' stroke='%23fff' stroke-width='1.4' stroke-linejoin='round'/%3E%3Cpath d='M20.5 14 29.5 30 11.5 30Z' fill='%23B45309' stroke='%23fff' stroke-width='1.6' stroke-linejoin='round'/%3E%3Cpath d='M19.4 19.8h2.2v5.2h-2.2zM19.4 26.6h2.2v2.2h-2.2z' fill='%23fff'/%3E%3C/svg%3E") 1 1, help; } .hint{ padding:12px 16px;border-top:1px dashed rgba(36,52,71,.18); font-size:.8rem;color:var(--muted);line-height:1.7; } /* Bedienung statt Notation — abgesetzt, damit die Legende ihre Rolle behält. */ .hint-op{margin-top:8px;padding-top:8px;border-top:1px dashed rgba(36,52,71,.18)} .hint code{ font-family:'IBM Plex Mono',monospace;font-size:.75rem; background:var(--paper);border:1px solid rgba(36,52,71,.12); border-radius:4px;padding:0 5px; } .hint .or-code{color:var(--or);border-color:rgba(15,118,110,.4)} .diagram{ flex:1 1 auto;min-height:0; padding:28px 16px 24px; overflow:auto; background:transparent; } .panel.right{background:rgba(255,255,255,.6);position:relative} .warnings{padding:0 16px 14px;font-size:.8rem;color:var(--warn)} .warnings div{margin-top:4px} /* Personen-Leiste (SPEC §9/D87): je Person eine Pille mit Belastungs-Balken, Tipp schaltet die Personen-Linse. Erscheint nur bei Dokumenten mit @-Tags; auf dem Telefon scrollt sie waagerecht statt umzubrechen (kein Aufklapp-Inhalt darin, die D50-Falle greift also nicht). */ .peoplebar{ display:flex;gap:8px;align-items:center;flex-wrap:wrap; padding:0 16px 10px; } .pbperson{ display:flex;gap:7px;align-items:center; border:1px solid rgba(36,52,71,.18);border-radius:999px; background:var(--card);cursor:pointer; padding:3px 10px 3px 4px;font:inherit; } .pbperson:hover{border-color:var(--or)} .pbperson[aria-pressed="true"]{border-color:var(--or);box-shadow:0 0 0 1.5px var(--or);background:rgba(15,118,110,.06)} .pbperson .tag{box-shadow:none} /* „ohne Zuständigen": gestrichelte Pille — es ist ein Loch, keine Person. */ .pbperson .tag.nobody{border-style:dashed;color:var(--muted);background:var(--card)} .pbbar{ width:64px;height:6px;border-radius:999px;overflow:hidden; background:rgba(36,52,71,.1); } .pbfill{display:block;height:100%;background:var(--or);border-radius:999px} .pbpct{font-size:.68rem;color:var(--muted);min-width:3ch;text-align:right} body.mobile .peoplebar{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;padding:0 10px 8px} .panel.collapsed .peoplebar{display:none} .empty{color:var(--muted);font-size:.85rem;padding:8px 4px} /* Zoom-Steuerung: schwebt unten rechts über dem Diagramm. */ .zoomctl{ position:absolute;right:6px;bottom:6px;z-index:5; display:inline-flex;align-items:center;gap:2px;padding:2px; background:var(--card);border:1px solid rgba(36,52,71,.18); border-radius:8px;box-shadow:0 1px 4px rgba(36,52,71,.14); height:28px; } .zoomctl button{ border:0;background:transparent;cursor:pointer;color:var(--muted); font-family:'IBM Plex Mono',monospace;font-size:.82rem;line-height:1; padding:4px 8px;border-radius:6px;min-width:30px; } #zoomReset{min-width:48px;font-size:.72rem} .zoomctl button:hover{background:var(--paper);color:var(--or)} .zoomToggle{display:none;padding:4px 8px} .zoomToggle svg{width:16px;height:16px} .zoomctl.collapsed .zoomToggle{display:block} .zoomctl.collapsed .zoomcontrols{display:none} .panel.collapsed .zoomctl{display:none} /* ---------- Baum ---------- */ .tree{width:max-content;margin:0 auto;list-style:none;position:relative} .tree ul{list-style:none} .tree li{display:flex;flex-direction:column;align-items:center;position:relative} .tree > li + li{margin-top:36px} .node{ position:relative; color:var(--ink); background:var(--card); border:1.5px solid var(--line); border-radius:var(--r); padding:7px 14px; font-size:.88rem;font-weight:500;line-height:1.3; /* Lange Labels brechen um, statt den Baum in die Breite zu treiben (D64). Die Umbrüche setzt der RENDERER (wrapLabel: ~40 Zeichen je Zeile, gleichmäßig verteilt) als `\n` — pre-line macht sie sichtbar, und der Kasten schrumpft auf die längste balancierte Zeile. `max-width` + `overflow-wrap` sind nur der Rückhalt für ein einzelnes Wort über der Grenze. Zentriert (Organigramm-Konvention). Die feste Zeilenhöhe bleibt — der 23-px-Abzweig (§9) trifft bei mehrzeiligen Knoten deterministisch die Mitte der ERSTEN Zeile. */ white-space:pre-line; max-width:32ch; text-align:center; overflow-wrap:break-word; box-shadow:0 1px 2px rgba(36,52,71,.08); } a.node{text-decoration:none;cursor:pointer} a.node:hover{box-shadow:0 2px 8px rgba(36,52,71,.22)} /* Falt-Zeichen (SPEC §9, D38): ▾ offen / „▸ n" eingeklappt, Klickziel fürs Umklappen. Gedeckt in Grau — es ist Bedienelement, keine Statusaussage; beim Zeigen färbt es sich zur Tinte. */ /* Als gerahmter Chip statt bloßem Glyph (D64): das nackte ▾ war ~10px klein und kaum zu treffen. Die Höhe ist bewusst gedeckelt (feste line-height + kein vertikales Padding): Der Chip darf die Zeilenbox des Knotens NICHT über die festen 18,3px heben, sonst wandert die 23-px-Abzweig-Geometrie (§9) um Halbpixel. */ .node .fold{ cursor:pointer;color:var(--line); font-size:.75em;margin-right:6px; display:inline-block;padding:0 6px;line-height:14px; border:1px solid var(--muted);border-radius:5px; background:#fff; user-select:none;-webkit-user-select:none; } /* Deckendes Weiß plus Tinte-Glyphe: Die halbtransparente Füllung der ersten Fassung verschwand auf den Pastell-Statusfarben („weiß auf hell") — der Chip muss sich auf ALLEN acht Füllungen abheben, und Weiß mit sattem Rand tut das; auf dem weißen Neutral-Knoten trägt der Rand allein. */ .node .fold:hover{color:var(--ink);border-color:var(--line);background:#fff} /* Der Wurzelknoten bekommt DENSELBEN Chip wie alle anderen — die frühere helle Umkehrung (weiße Glyphe auf durchscheinendem Weiß) war für den dunklen Tinte-Wurzelknoten gebaut; trägt die Wurzel aber einen Status, gewinnt dessen Pastell-Füllung (.node.st-* ist spezifischer, D39), und Weiß auf Rosé war praktisch unsichtbar. Der deckend weiße Chip hebt sich auch auf der dunklen Wurzel ab (wie die hellen Tag-Pillen dort). */ /* ”-Marke (SPEC §9, D40): der Knoten trägt eine Beschreibung — im Tooltip nachlesbar. Spiegelt das "-Zeichen der Notation; gedeckt, kein Klickziel. */ .node .desc-mark{color:var(--muted);margin-left:4px;font-weight:600} /* Die helle Textfassung gilt nur dem DUNKLEN Wurzelknoten — mit Status ist die Wurzel pastellfarben (siehe Falt-Chip oben), und Weiß darauf wäre unsichtbar. „Dunkel" heißt: keine Statusklasse. */ .root-node:not(.st-idee):not(.st-geplant):not(.st-arbeit):not(.st-durchstich):not(.st-fertig):not(.st-prod):not(.st-highrisk):not(.st-verworfen) .desc-mark{color:rgba(255,255,255,.75)} /* Knoten-ID vor dem Titel (D56). Zurückgenommen und in der Mono-Schrift des Textfelds: Sie ist die Adresse, nicht der Name — der Titel soll die Zeile weiter anführen. Kein `margin-right`; das trennende Leerzeichen steht im Markup, damit der Grafikexport (er liest den Knotentext) es mitbekommt. */ .node .nid{ font-family:'IBM Plex Mono',monospace;font-size:.86em;font-weight:500; color:var(--muted); } /* Nur die dunkle (statuslose) Wurzel — Begründung beim Falt-Chip oben. */ .root-node:not(.st-idee):not(.st-geplant):not(.st-arbeit):not(.st-durchstich):not(.st-fertig):not(.st-prod):not(.st-highrisk):not(.st-verworfen) .nid{color:rgba(255,255,255,.75)} /* Ticket-Referenz im Label (SPEC §11): als Ganzes gehalten — der Browser bräche eine zu breit geratene Zeile sonst am Bindestrich mitten im Token (`#US-`/`123`). Die Spanne setzt render.js (markTicketRefs). */ .node .tref{white-space:nowrap} /* Abweichungs-Marke (D91-Nachtrag 10): Das Ticket hinter dieser Ref sagt etwas anderes als die Statusbox — die Ref selbst wird warnfarben, kein neues Element in den ohnehin dichten Knoten-Ecken. Nur Anzeige aus der Sitzung: nicht im Grafikexport (der liest die Knotenfarbe, nicht die Spanne) und nicht im Druck. */ .node .tref-diff{color:var(--warn);font-weight:600} /* Das Badge für Refs, die (nur) die Knoten-ID sind — dort steht die Ref sonst nirgends im Knoten. Klein und nachgestellt wie die ”-Marke. */ .node .tref-badge{margin-left:4px;font-size:.86em} /* Der Knopf trägt das Zeichen selbst statt eines Icons — in der Größe der SVGs daneben, damit die Kopfzeile ihre Höhe behält. */ .copybtn.idsbtn span{ display:block;width:15px;height:15px;line-height:15px;text-align:center; font-family:'IBM Plex Mono',monospace;font-size:14px;font-weight:600; } /* Sichtbarer Tastatur-Fokus (Knoten sind fokussierbar: Fokusreihenfolge = Lese-/Dokumentreihenfolge; Screenreader liest den aria-label). */ .node:focus-visible{outline:2px solid var(--or);outline-offset:2px;box-shadow:0 2px 8px rgba(36,52,71,.22)} /* Knoten der Cursor-Zeile im Editor (D25). Weißer Halo + Tinte-Ring: hebt sich von allen Pastell-Status UND vom dunklen Wurzelknoten ab und ist vom petrolfarbenen Fokusring unterscheidbar. Rein visuelle Editierhilfe — nicht im Druck. Der `#out`-Präfix ist nötig: `ul.or .node{box-shadow:none}` (weiter unten) ist spezifischer als `.node.current` und schluckte den Ring sonst überall unterhalb einer any-of-Gruppe. */ /* Der Ring allein war zu leise (D25-Nachtrag): Er benutzt denselben Kanal, den schon jeder Knoten belegt — **jeder Status hat einen Rahmen** (SPEC §4). Ein weiterer Ring in einem Feld aus lauter gerahmten Kästchen ist ein Unterschied im Grad, nicht in der Art. Deshalb kommt **Tiefe** dazu: Schlagschatten plus leichte Vergrößerung. Das ist der einzige Kanal, den im Diagramm noch nichts belegt (nichts anderes wirft Schatten oder hebt sich), kollidiert also mit keiner Farbcodierung und wirkt über allen acht Statusfarben gleich. `transform` ändert **kein Layout** — die Linien bleiben stehen; und weil um die **Mitte** skaliert wird, bleibt auch die Knotenmitte unverändert, auf die `alignStems()` und `drawCheapPath()` messen. `z-index`, weil Schatten und vergrößerter Knoten AUSSERHALB der Box liegen (dasselbe wie bei `.fresh`). */ #out .node.current{ box-shadow:0 0 0 2px var(--card),0 0 0 4px var(--ink),0 10px 20px -6px rgba(36,52,71,.5); transform:scale(1.04);z-index:3; } /* Fokusmarke `!!!` (SPEC §1, D32): **Strahlenkranz in Petrol**, nach dem Muster der Neuheiten-Markierung (D28), nur in der Akzentfarbe der App statt in Gelb. Der geteilte Ring mit der Cursor-Zeile war zu leise — „das erkennt man zu wenig" (Nutzer) —, und ein Ring allein wächst beim Zoomen nicht mit dem Blickbedürfnis. Der Kranz löst zugleich die in D32 hingenommene Schwäche: Cursor-Zeile (Tinte, innen) und fremde Marke (Petrol, außen) sind jetzt unterscheidbar. Kein Blinken (WCAG 2.2.2/2.3.1, vgl. D28), und `z-index`, weil der Schein AUSSERHALB der Knotenbox liegt — ohne ihn malt jedes später kommende Geschwister seinen Hintergrund darüber und schneidet ihn ab. */ /* Der Schein ist **helles** Teal (#14B8A6), nicht das dunkle Petrol des Rings: Ein dunkler Schein auf weißem Grund liest sich als Schatten, ein heller als Licht — deshalb wirkte die erste Fassung leiser als der gelbe Kranz aus D28, obwohl die Geometrie dieselbe war. Der Ring bleibt dunkles Petrol und ordnet die Marke der Akzentfarbe zu; das Leuchten macht die Reichweite. */ #out .node.focusmark{box-shadow:0 0 0 3px var(--or),0 0 20px 7px rgba(20,184,166,.85);z-index:2} /* Beides zugleich: Cursor-Zeile innen (Tinte), fremde Marke außen (Petrol). Die Erhebung der Cursor-Zeile gilt in jeder Kombination — der Schlagschatten wird deshalb hier mit angehängt (das `transform` erbt die Regel oben). */ #out .node.focusmark.current{box-shadow:0 0 0 2px var(--card),0 0 0 4px var(--ink),0 0 20px 7px rgba(20,184,166,.85),0 10px 20px -6px rgba(36,52,71,.5);z-index:3} /* Neu in Produktion seit dem letzten Besuch (D28): gelber Strahlenkranz um den Knoten. Bewusst ein Schein nach AUSSEN statt Farbe im Knoten — die Füllung gehört dem Status (SPEC §4) und bleibt lesbar. Kein Blinken: WCAG 2.2.2/2.3.1 und es zöge dauerhaft den Blick. `#out`-Präfix wegen `ul.or .node`. */ /* `z-index`, weil der Schein AUSSERHALB des Knotens liegt: ohne ihn malt jedes später kommende Geschwister seinen undurchsichtigen Hintergrund darüber und schneidet den Kranz einseitig ab (in engen Layouts bleibt nur eine gelbe Kante stehen — sieht aus wie ein halber Hintergrund statt eines Rahmens). */ #out .node.fresh{box-shadow:0 0 0 2px #FACC15,0 0 16px 5px rgba(234,179,8,.65);z-index:2} /* Beides zugleich: Cursor-Zeile innen (Tinte), Neuheit außen (gelb). */ #out .node.fresh.current{box-shadow:0 0 0 2px var(--card),0 0 0 4px var(--ink),0 0 18px 6px rgba(234,179,8,.65),0 10px 20px -6px rgba(36,52,71,.5);z-index:3} /* Neu in Produktion UND jemand zeigt darauf: Ring in Petrol (der bewusste Zuruf, innen), Kranz in Gelb (die stehende Nachricht, außen). Zwei Scheine nach außen gingen nicht — einer muss der Ring sein. Steht bewusst **nach** `.node.focusmark.current`: gleiche Spezifität (drei Klassen), und wenn alles drei zutrifft, soll Zuruf + Neuheit gewinnen, nicht die eigene Cursor-Zeile. */ #out .node.fresh.focusmark{box-shadow:0 0 0 3px var(--or),0 0 20px 7px rgba(234,179,8,.65);z-index:2} /* Puls beim Zeilenwechsel (D25-Nachtrag): Man verliert den Knoten beim **Bewegen**, nicht im Stillstand — also meldet er sich genau dann einmal. Einmalig, kein Blinken: WCAG 2.2.2 zielt auf Blinkendes, 2.3.1 auf Flackern über 3 Hz; ein einzelner Durchlauf ist keins von beidem (vgl. D28). Ausgelöst wird er in `highlightCurrentNode()` **nur** bei geändertem `caretLine` — sonst pulste er bei jedem Tastendruck. Bewusst NICHT über `box-shadow` animiert: die Kombinationen mit `.fresh` und `.focusmark` haben je eigene Schatten-Listen, eine Animation darauf ließe den gelben bzw. petrolfarbenen Kranz für die Dauer des Pulses verschwinden. Stattdessen zwei Kanäle, die niemand sonst benutzt: ein kurzer Hüpfer im `transform` und ein auslaufender Ring auf dem freien `::after` (`::before` gehört dem Optional-Kreis, D29). */ @keyframes nodeLift{0%{transform:scale(1.04)}40%{transform:scale(1.11)}100%{transform:scale(1.04)}} @keyframes nodeRipple{from{transform:scale(1);opacity:.5}to{transform:scale(1.4);opacity:0}} #out .node.current.pulse{animation:nodeLift .36s ease-out 1} /* `both` ist Pflicht: ohne Fill-Mode schnappt der Ring am Ende auf die Anfangsdeckkraft zurück und bliebe stehen. */ #out .node.current.pulse::after{ content:'';position:absolute;inset:-3px;border-radius:calc(var(--r) + 3px); border:2px solid var(--ink);pointer-events:none; animation:nodeRipple .45s ease-out 1 both; } @media (prefers-reduced-motion: reduce){ #out .node.current.pulse{animation:none} #out .node.current.pulse::after{display:none} } /* Während `diagramToSvg()` misst: Erhebung und Puls abschalten — der Export zeichnet aus der Live-Geometrie nach, und die Vergrößerung ist eine Editierhilfe, keine Aussage über den Plan (D25). */ #out.exporting .node.current{transform:none;animation:none} #out.exporting .node.current::after{display:none} /* Alt gedrückt = Sprung-Geste scharf: Cursor an allen Knoten, Petrol-Ring am Knoten unter dem Zeiger. Auf verlinkten Knoten ist das die einzige Rückmeldung, weil dort der einfache Klick dem Link gehört (D25). */ #out.alt .node{cursor:alias} #out.alt .node:hover{box-shadow:0 0 0 2px var(--card),0 0 0 4px var(--or)} /* Langer Druck „scharf": derselbe Petrol-Ring wie unter dem Alt-Zeiger. */ #out .node.armed{box-shadow:0 0 0 2px var(--card),0 0 0 4px var(--or)} /* Auf Touch ist der lange Druck die Sprung-Geste — die native Textauswahl und das Link-Callout des Browsers starten zur selben Zeit und rissen sonst die Auswahl im Textfeld wieder an sich (D25). */ @media (hover:none) and (pointer:coarse){ .node{-webkit-touch-callout:none;-webkit-user-select:none;user-select:none} } .ext{font-size:.72em;margin-left:5px;opacity:.55} .size{ position:absolute;top:-9px;right:-9px; font-family:'IBM Plex Mono',monospace; font-size:.58rem;font-weight:500;letter-spacing:.02em; background:var(--or);color:#fff; border:1.5px solid var(--card); border-radius:999px;padding:1px 6px;line-height:1.4; } .risk{ position:absolute;top:-10px;left:-10px; width:18px;height:18px; display:flex;align-items:center;justify-content:center; font-size:.62rem;line-height:1; background:var(--card);color:#F97316; border:1.5px solid #F97316;border-radius:999px; box-shadow:0 0 0 1.5px var(--card); } .tags{ position:absolute;right:-9px;bottom:-9px; display:flex;gap:3px; } /* Diskrepanz-Marke des effektiven Status (SPEC §9, D39): die eigene Statusbox in den eigenen §4-Farben (nutzt die .chip-Farbregeln der Legende), unten links — die letzte freie Knoten-Ecke. */ .node .chip.ownst{ position:absolute;left:-9px;bottom:-9px; font-size:.58rem;font-weight:500;letter-spacing:.02em; padding:1px 5px;line-height:1.4;border-radius:999px; } .tag{ font-family:'IBM Plex Mono',monospace; font-size:.58rem;font-weight:500;letter-spacing:.02em; background:#EEF1F4;color:var(--ink); border:1.5px solid var(--line); border-radius:999px;padding:1px 6px;line-height:1.4; box-shadow:0 0 0 1.5px var(--card); } /* Zuständigen-Engpass (SPEC §9/D71): die Pille der Person, die mehr als die Hälfte der offenen Pfad-Arbeit trägt — Warnfarbe wie das Größen-Konflikt-Badge (D62); der Export liest die gemessenen Farben. */ .tag.overload{background:var(--warn);color:#fff;border-color:var(--warn)} /* Personen-Linse (D87): die Pille der gewählten Person in Petrol — welche der sichtbaren Knoten ihr gehören. Die Engpass-Warnfarbe gewinnt (der Renderer vergibt nur eine der beiden Klassen). */ .tag.lens{background:var(--or);color:#fff;border-color:var(--or)} .ghost-node{ position:relative;margin-top:14px; border:1.5px dashed var(--warn); color:var(--warn);background:rgba(180,83,9,.06); border-radius:var(--r);padding:4px 12px; font-size:.74rem;font-style:italic;white-space:nowrap; } .ghost-node::before{ content:'';position:absolute;top:-14px;left:50%;height:14px; border-left:2px dashed var(--warn); } .root-node{background:var(--ink);border-color:var(--ink);color:#fff;font-weight:600;padding:9px 20px} /* Günstigster Pfad (Inversion): nicht benötigte Knoten (nicht-gewählte any-of-Alternativen samt Teilbaum) treten zurück (blass, entsättigt); der günstige Pfad hebt sich dadurch von allein ab. */ .cheap-on .node:not(.cheap){opacity:.32;filter:saturate(.4)} /* „Neu in Produktion" darf die Pfad-Inversion nicht wegdimmen: opacity und saturate treffen sonst auch den gelben Kranz (0,32 Deckkraft ⇒ praktisch unsichtbar), und gerade bei einer nicht gewählten Alternative ist „das ist jetzt live" die interessantere Nachricht (D28). */ .cheap-on .node:not(.cheap).fresh{opacity:1;filter:none} /* Dasselbe für die Fokusmarke `!!!` (SPEC §1), und hier noch zwingender: Ein Zeigefinger, der auf einen ausgeblassten Knoten zeigt, zeigt auf nichts — und „sollten wir diese verworfene Alternative noch einmal ansehen?" ist einer der wahrscheinlichsten Gründe, überhaupt zu zeigen. Anders als beim optionalen Knoten (D29), wo das Zurücktreten die Aussage IST. */ .cheap-on .node:not(.cheap).focusmark{opacity:1;filter:none} /* Die Cursor-Zeile auf einem zurückgetretenen Knoten (D25-Nachtrag 4): Nur der RING wird hervorgehoben, die Füllung bleibt blass. Die erste Fassung stellte den ganzen Knoten auf volle Stärke — dann verlor er beim bloßen Hineinsetzen des Cursors seine Pfad-Aussage („nicht nötig"). Ganz gedimmt lassen geht aber auch nicht: opacity/filter am Element treffen den box-shadow mit, der Ring wäre unsichtbar (der ursprüngliche D25-Befund). Deshalb bleibt der Knoten voll deckend, und die Blässe wird NACHGEBILDET: - background-image legt einen Papier-Wasch (68 %) ÜBER die Statusfarbe — rechnerisch dasselbe wie 0,32 Deckkraft über dem hellen Grund; - Text- und Rahmenfarbe wechseln auf das gedimmte Grau (der Unterschied der acht Status-Rahmenfarben ist bei 0,32 ohnehin nicht mehr zu sehen); - die Kinder (Badges, Pillen, Chips) dimmen einzeln per opacity — genau so, wie es vorher die Element-Deckkraft tat. `.ext` ist ausgenommen: es erbt die schon gefadete Textfarbe und wäre sonst doppelt gedimmt. fresh/focusmark/done sind ausgeschlossen — deren Ausnahmen (volle Stärke) haben je eigene Gründe und gelten weiter. Der Puls-Ring (::after) und der Schlagschatten bleiben unberührt in voller Stärke. */ .cheap-on .node:not(.cheap):not(.fresh):not(.focusmark):not(.done).current{ opacity:1;filter:none; background-image:linear-gradient(rgba(243,246,249,.68),rgba(243,246,249,.68)); border-color:rgba(107,122,140,.4); color:rgba(36,52,71,.35); } .cheap-on .node:not(.cheap):not(.fresh):not(.focusmark):not(.done).current > :not(.ext){ opacity:.32;filter:saturate(.4); } /* Erledigtes tritt NIE zurück (D46-Nachtrag). D46 hatte das schon entschieden — „sie auszublassen hieße, sie sähen aus wie eine verworfene Alternative" —, die Regel griff aber nur für erledigte Knoten AUF dem Pfad. Ein fertiger OPTIONALER Knoten (`+`, D29: nie auf dem Pfad) und eine fertige, nicht gewählte Alternative fielen durch das Raster: `#ed.fresh` ist gebaut und deployed, sah aber aus wie etwas, das nie passieren wird. Blassheit heißt jetzt einheitlich „hier ist nichts getan und wird auch nichts getan". */ .cheap-on .node:not(.cheap).done{opacity:1;filter:none} /* implizit als M angenommene Größe: invertiertes Badge (weiß statt petrol) */ .size.implicit{background:var(--card);color:var(--or);border-color:var(--or)} /* Größen-Konflikt (SPEC §5/D62): die Teilpakete übersteigen zusammen die angegebene Größe — das Badge wechselt auf die Warnfarbe, der weiße Trennrand zur Knotenkante bleibt. */ .size.conflict{background:var(--warn)} /* Günstigster-Pfad-Linie: gestrichelt, geschwungen, petrol — fädelt durch die Endknoten (Blätter) des Pfads. Zwei Overlays in #out (erben dessen zoom): die kräftige Linie liegt HINTER den Knoten (nur in den Lücken sichtbar), eine abgetönte Kopie liegt DAVOR (deutet den Verlauf beim Durchschreiten von Knoten nur schwach an). Kleine Stationspunkte an den Blättern zeigen, welche Knoten wirklich auf dem Pfad enden — durchquerte Fremdknoten tragen keinen Punkt. Reihenfolge im DOM steuert vorn/hinten. */ .cheap-overlay{position:absolute;left:0;top:0;overflow:visible;pointer-events:none} /* Die VORDERE Ebene muss über JEDEN Knoten — auch über die, die sich selbst eine Stapelposition nehmen: Cursor-Zeile (`z-index:3`, D25), Strahlenkränze (`z-index:2`, D28/D32). Ohne das deckt ausgerechnet der hervorgehobene Knoten seinen eigenen Stationspunkt zu. „Vorn" heißt vorn. (Die Abhängigkeits-Kanten hatten aus demselben Grund einmal `.dep-front` mit `z-index:4`; sie liegen jetzt vollständig hinten — D41-Nachtrag 2.) */ .cheap-overlay.cheap-front{z-index:5} /* Querverbindungen der Abhängigkeiten (SPEC §9, D41): blassgrau GEPUNKTET und gekrümmt — Punktierung und Krümmung unterscheiden sie doppelt von den Baumlinien (durchgezogen/gestrichelt, orthogonal) und halten sie leise (D41-Nachtrag). Hervorgehobene Kanten (fokussierter Knoten/Cursor-Zeile) durchgezogen in Tinte. */ .dep-overlay{position:absolute;left:0;top:0;overflow:visible;pointer-events:none} .dep-edge{fill:none;stroke:var(--muted);stroke-width:1.5;opacity:.4; stroke-linecap:round;stroke-dasharray:.1 3} /* Offene Pfeilspitze — grau und dünn wie die Linie, kein gefülltes Dreieck. */ .dep-arrow{fill:none;stroke:var(--muted);stroke-width:1.5;opacity:.4; stroke-linecap:round;stroke-linejoin:round} .dep-edge.hl{stroke:var(--line);stroke-width:2;opacity:.9;stroke-dasharray:none} .dep-arrow.hl{stroke:var(--line);stroke-width:2;opacity:.9} .cheap-path{fill:none;stroke:var(--or);stroke-width:2.5;stroke-linecap:round; stroke-dasharray:8 6;opacity:.9} .cheap-path.faint{opacity:.2} /* Stationspunkt: groß, aber blass gefüllt — der Knotentext bleibt lesbar */ .cheap-dot{fill:var(--or);fill-opacity:.2;stroke:var(--or);stroke-opacity:.35;stroke-width:1.5} /* ---------- Knoten-Fenster für Touch (SPEC §6/§9, D52) ---------- Auf einem Zeigergerät zeigt der `title` den Tooltip; ohne Zeiger gibt es keinen. Der einfache Tipp öffnet deshalb dieses Fenster mit demselben Inhalt — und mit dem, was ein `title` nicht kann: Absatz, echte Trennlinie, Knöpfe. `position:fixed` (JS setzt left/top aus `getBoundingClientRect()`, die den `zoom` von #out schon enthält): Ein Kind von #out erbte dessen `zoom` und würde von dessen `overflow` beschnitten (D50). */ .nodetip{ position:fixed;z-index:60;max-width:min(21rem, calc(100vw - 24px)); background:var(--card);color:var(--ink); border:1px solid rgba(36,52,71,.15);border-radius:10px; box-shadow:0 8px 24px rgba(36,52,71,.3); padding:10px 30px 10px 12px;font-size:.78rem;line-height:1.45; } /* Mit Taiga-Knöpfen darf das Fenster breiter werden, damit die Aktionen nebeneinander stehen (D91-Nachtrag 11); die 21rem bleiben das Maß für reine Text-Fenster. */ .nodetip.tipwide{max-width:min(31rem, calc(100vw - 24px))} .nodetip[hidden]{display:none} .nodetip-x{ position:absolute;top:2px;right:2px;width:26px;height:26px; display:flex;align-items:center;justify-content:center; background:none;border:0;border-radius:6px;cursor:pointer; color:var(--muted);font-size:1.05rem;line-height:1; } .nodetip-x:hover{color:var(--ink);background:rgba(36,52,71,.06)} /* Absätze kommen aus Leerzeilen der Notation (§1); JS baut sie zu

. */ .nodetip-desc p + p{margin-top:.55em} /* Die echte Linie an der Stelle, an der der Tooltip 24 `─` zeichnen musste. */ .nodetip-facts{ margin-top:8px;padding-top:7px;border-top:1px solid rgba(36,52,71,.15); color:var(--muted);font-size:.72rem; } /* Ohne Beschreibung gibt es nichts zu trennen — der Strich stünde sonst als erste Zeile im Fenster. Dieselbe Regel wie im Tooltip (D40-Nachtrag). */ .nodetip-facts:first-child{margin-top:0;padding-top:0;border-top:0} .nodetip-link{ display:inline-flex;align-items:center;gap:5px;margin-top:9px; padding:5px 10px;border:1px solid var(--or);border-radius:7px; color:var(--or);text-decoration:none;font-size:.74rem;font-weight:500; } /* Taiga-Aktionen im Knoten-Fenster (D91) — dasselbe Knopf-Idiom wie der ↗-Link, nur als