  /*! 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)}
  /* 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}
  /* 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 --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}
  /* 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. */
  /* 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}
  /* 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;
    /* 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}
  /* „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}
  /* Falt-Durchschalter (SPEC §9/D75): vier Icons, sichtbar ist das des
     NÄCHSTEN Schritts — `data-next` setzt updateFoldBtn(). Zwei Klassen wie
     beim Pad-Wähler, 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;
  }
  /* 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). */
  /* Wähler + Snapshot-Knopf als eine Gruppe: Sie trägt das `margin-right:auto`
     (schiebt die übrigen Knöpfe nach rechts) und ist der Positionsbezug des
     Snapshot-Menüs — dadurch klappt es unter seinem Knopf auf, ohne dass JS
     rechnen muss. Am Wähler selbst darf das auto-Margin nicht mehr stehen,
     sonst schöbe es den Snapshot-Knopf von ihm weg. */
  .docgroup{
    display:inline-flex;align-items:center;gap:4px;
    margin-right:auto;max-width:62%;min-width: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}
  .doctrigger{
    display:inline-flex;align-items:center;gap:5px;cursor:pointer;
    min-width:0;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}
  /* Rückmeldung fürs stille In-Place-Speichern (Strg+S, D74): Petrol-Blitz
     samt Haken am Dokumentnamen — das Haus-Idiom von flashBtn (D54). */
  .doctrigger.done{color:var(--or)}
  .doctrigger.done::before{content:"✓ ";flex:0 0 auto}
  .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;
  }
  /* Snapshot-Menü (D54): wie das Dokument-Menü, aber am rechten 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}
  .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)}
  /* „Original wiederherstellen" (D22-Nachtrag): sichtbar, sobald ein
     mitgeliefertes Dokument aktiv ist; ausgegraut, solange es unverändert ist. */
  .docact:disabled{opacity:.45;cursor:default}
  .docact:disabled:hover{border-color:rgba(36,52,71,.2);color:var(--muted)}
  .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}
  /* 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;
  }
  /* 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. 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;
  }
  .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}
  /* 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}
  .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 dunkle Wurzelknoten braucht die helle Umkehrung. */
  .root-node .fold{color:#fff;border-color:rgba(255,255,255,.7);background:rgba(255,255,255,.16)}
  .root-node .fold:hover{color:#fff;border-color:#fff;background:rgba(255,255,255,.26)}
  /* ”-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}
  .root-node .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);
  }
  .root-node .nid{color:rgba(255,255,255,.75)}
  /* 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)}
  .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}
  /* Und dasselbe für die Cursor-Zeile (D25-Nachtrag) — die Ausnahme fehlte hier
     als einzige, obwohl der Fall der häufigste ist: Der Umschalter ist
     voreingestellt an, und jeder optionale Knoten und jede nicht gewählte
     Alternative ist „nicht auf dem Pfad" (im mitgelieferten Beispiel 7 von 18
     Knoten). Gemessen auf `+ [?] Dark mode`: Deckkraft 0,32 — der Ring war dort
     praktisch unsichtbar. Wo der Cursor steht, muss sichtbar bleiben, egal wie
     der Pfad entschieden hat; anders als beim optionalen Knoten (D29), wo das
     Zurücktreten die Aussage IST, ist hier gar keine Aussage über den Plan
     gemeint — es ist eine Editierhilfe. */
  .cheap-on .node:not(.cheap).current{opacity:1;filter:none}
  /* 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;
  }
  .nodetip[hidden]{display:none}
  .nodetip-x{
    position:absolute;top:2px;right:2px;width:26px;height:26px;
    display:flex;align-items:center;justify-content:center;
    background:none;border:0;border-radius:6px;cursor:pointer;
    color:var(--muted);font-size:1.05rem;line-height:1;
  }
  .nodetip-x:hover{color:var(--ink);background:rgba(36,52,71,.06)}
  /* Absätze kommen aus Leerzeilen der Notation (§1); JS baut sie zu <p>. */
  .nodetip-desc p + p{margin-top:.55em}
  /* Die echte Linie an der Stelle, an der der Tooltip 24 `─` zeichnen musste. */
  .nodetip-facts{
    margin-top:8px;padding-top:7px;border-top:1px solid rgba(36,52,71,.15);
    color:var(--muted);font-size:.72rem;
  }
  /* Ohne Beschreibung gibt es nichts zu trennen — der Strich stünde sonst als
     erste Zeile im Fenster. Dieselbe Regel wie im Tooltip (D40-Nachtrag). */
  .nodetip-facts:first-child{margin-top:0;padding-top:0;border-top:0}
  .nodetip-link{
    display:inline-flex;align-items:center;gap:5px;margin-top:9px;
    padding:5px 10px;border:1px solid var(--or);border-radius:7px;
    color:var(--or);text-decoration:none;font-size:.74rem;font-weight:500;
  }
  /* Die Spitze zeigt auf den Knoten — ohne sie wäre bei dicht stehenden Knoten
     nicht zu sehen, welcher gemeint ist. Ein gedrehtes Quadrat mit zwei Kanten:
     so erbt es Rahmen UND Schatten des Fensters. JS setzt --tipx (Spitze) und
     dreht sie per .above nach unten, wenn das Fenster über dem Knoten sitzt. */
  .nodetip-arrow{
    position:absolute;top:-6px;left:var(--tipx, 50%);width:11px;height:11px;
    margin-left:-5.5px;background:var(--card);
    border-left:1px solid rgba(36,52,71,.15);
    border-top:1px solid rgba(36,52,71,.15);
    transform:rotate(45deg);
  }
  .nodetip.above .nodetip-arrow{
    top:auto;bottom:-6px;
    border:0;border-right:1px solid rgba(36,52,71,.15);
    border-bottom:1px solid rgba(36,52,71,.15);
  }
  /* Solange das Fenster offen ist, trägt sein Knoten einen Petrol-Ring — wie
     der „scharfe" Knoten beim langen Druck (D25). #out-Präfix nötig, weil
     `ul.or .node{box-shadow:none}` spezifischer ist. */
  #out .node.tipped{box-shadow:0 0 0 3px rgba(15,118,110,.55)}

  /* ---------- ID-Vorschläge beim Tippen von Abhängigkeiten (D63) ----------
     `position:fixed` auf <body> wie das Knoten-Fenster: In einem Vorfahren
     mit `overflow` würde die Liste geklippt (D50). Die IDs in der
     Mono-Schrift des Textfelds, der Titel als gedämpfter Kontext daneben. */
  .aclist{
    position:fixed;z-index:70;min-width:160px;max-width:min(24rem, calc(100vw - 16px));
    max-height:12.5rem;overflow-y:auto;
    background:var(--card);border:1px solid rgba(36,52,71,.15);border-radius:8px;
    box-shadow:0 8px 24px rgba(36,52,71,.25);padding:4px;
  }
  .aclist[hidden]{display:none}
  .acitem{
    display:flex;align-items:baseline;gap:8px;padding:4px 8px;border-radius:5px;
    cursor:pointer;white-space:nowrap;
  }
  .acitem.sel,.acitem:hover{background:rgba(15,118,110,.12)}
  .acid{font-family:'IBM Plex Mono',monospace;font-size:.74rem;color:var(--ink)}
  .aclabel{
    font-size:.7rem;color:var(--muted);
    overflow:hidden;text-overflow:ellipsis;max-width:14rem;
  }
  /* Visuell versteckt, für Screenreader da — die Live-Region der Vorschläge. */
  .vh{
    position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;
    clip-path:inset(50%);white-space:nowrap;
  }

  /* --- ALL OF: Kinder nebeneinander, durchgezogener Verteiler --- */
  ul.and{display:flex;align-items:flex-start;position:relative;padding-top:14px}
  ul.and::before{
    content:'';position:absolute;top:0;left:50%;height:14px;border-left:2px solid var(--line);
  }
  ul.and>li{padding:16px 12px 0}
  /* Der Stiel liegt bei `--stem-x` — normalerweise 50 % des <li>, was die
     Knotenmitte ist, solange der Knoten im <li> zentriert steht. Bei
     `li.has-or` ist er das NICHT (linksbündig, damit die any-of-Leiste unter
     ihm aufsetzt) und der Teilbaum macht das <li> breiter als den Knoten: der
     Stiel liefe daneben vorbei. `alignStems()` in app.js misst dort die
     Knotenmitte und setzt `--stem-x` — D10 in der Waagerechten. */
  ul.and>li::before,
  ul.and>li::after{
    content:'';position:absolute;top:0;
    left:0;right:calc(100% - var(--stem-x, 50%));
    border-top:2px solid var(--line);height:16px;
  }
  ul.and>li::after{left:var(--stem-x, 50%);right:0;border-left:2px solid var(--line)}
  ul.and>li:first-child::before,
  ul.and>li:last-child::after{border:0 none}
  ul.and>li:last-child::before{border-right:2px solid var(--line);border-radius:0 6px 0 0}
  ul.and>li:first-child::after{border-radius:6px 0 0 0}
  /* Einziges Kind: Normalerweise fallen der Stiel aus dem Elternknoten
     (`ul.and::before`, 50 % der Gruppe) und der Stiel zum Kind (`--stem-x`)
     zusammen — eine gerade Linie, keine Leiste nötig. Steht der Knoten aber
     nicht mittig in seiner Zelle (`li.has-or`, Treppe — dort misst
     `alignStems()` die Knotenmitte, D29-Nachtrag 2), liegen die beiden
     auseinander, und ohne Geschwister gibt es keine Sammelleiste, die sie
     verbände: die Linie riss ab (gemessen: 8,5 px). Deshalb hier ein kurzes
     Leiterstück genau zwischen den beiden — dieselbe L-Form, mit der ein
     äußeres Kind einer größeren Gruppe angeschlossen ist. Ohne `--stem-x` ist
     es null Pixel breit und damit unsichtbar. Diese Regeln müssen NACH den
     first/last-Regeln stehen: `li:only-child` ist beides, und bei gleicher
     Spezifität gewinnt die spätere. */
  ul.and>li:only-child{padding-top:14px}
  ul.and>li:only-child::before{
    border:0 none;border-top:2px solid var(--line);border-radius:0;height:14px;
    left:min(50%, var(--stem-x, 50%));
    right:calc(100% - max(50%, var(--stem-x, 50%)));
  }
  ul.and>li:only-child::after{
    border:0 none;border-left:2px solid var(--line);border-radius:0;height:14px;
    left:var(--stem-x, 50%);right:0;
  }

  /* --- ANY OF: durchgehend gestrichelte graue Linien (Haupt- + Abzweige) --- */
  li.has-or{align-items:flex-start}
  ul.or{
    display:flex;flex-direction:column;
    align-items:flex-start;
    position:relative;
    padding-top:14px;
  }
  ul.or::before{
    content:'';position:absolute;top:0;left:9px;height:14px;border-left:2px dashed var(--muted);
  }
  /* XOR (`=`, SPEC §3/§9, D35): Geometrie kommt komplett von `ul.or` (der
     Renderer gibt `class="or xor"` aus); `.xor` ergänzt nur die „1"-Plakette
     am Austritt der Sammelleiste — „genau eine". Grau wie die any-of-Führung
     (Gate-Codierung, keine Signalfarbe, D15); sitzt mittig auf dem
     14-px-Einlaufstück (left:9px = Leisten-x in allen Modi). */
  ul.xor::after{
    content:'1';
    position:absolute;left:9px;top:7px;transform:translate(-50%,-50%);
    width:13px;height:13px;border-radius:50%;
    background:var(--card);border:1.5px solid var(--muted);
    box-sizing:border-box;
    color:var(--muted);font-size:9px;font-weight:600;line-height:10px;
    text-align:center;
  }
  ul.or>li{position:relative;align-items:flex-start;padding:5px 0 5px 30px}
  /* Abzweig auf Höhe der Knoten-Mitte (5px li-Padding + halbe Knotenhöhe), nicht Mitte des Teilbaums */
  ul.or>li::before{
    content:'';position:absolute;left:9px;top:23px;width:21px;border-top:2px dashed var(--muted);
  }
  ul.or>li::after{
    content:'';position:absolute;left:9px;top:0;height:100%;border-left:2px dashed var(--muted);
  }
  ul.or>li:last-child::after{height:23px}
  /* all-of-Teilbaum UNTER einer any-of-Alternative (nur horizontal):
     schmal transponiert stapeln — Kinder untereinander mit linkem, solidem
     Verteiler (wie im kompakten Modus), statt breitem Fächer. Sonst zwingt der
     breite Fächer ein Zentrieren, das den Alternativknoten (und damit den
     ganzen Elternbaum) weit nach rechts schiebt. Passt zugleich stilistisch
     zur gestapelten any-of-Spalte darüber. Vertikal/kompakt haben ihre eigene
     `ul.and`-Behandlung und bleiben unberührt. */
  .tree:not(.vertical):not(.kompakt) ul.or>li.has-and>ul.and{
    flex-direction:column;align-items:flex-start;padding-top:14px;
  }
  .tree:not(.vertical):not(.kompakt) ul.or>li.has-and>ul.and::before{left:9px;height:14px}
  .tree:not(.vertical):not(.kompakt) ul.or>li.has-and>ul.and>li{padding:5px 0 5px 30px}
  .tree:not(.vertical):not(.kompakt) ul.or>li.has-and>ul.and>li::before{
    left:9px;right:auto;top:23px;width:21px;height:0;
    border:0;border-top:2px solid var(--line);border-radius:0;
  }
  .tree:not(.vertical):not(.kompakt) ul.or>li.has-and>ul.and>li::after{
    left:9px;right:auto;top:0;width:0;height:100%;
    border:0;border-left:2px solid var(--line);border-radius:0;
  }
  .tree:not(.vertical):not(.kompakt) ul.or>li.has-and>ul.and>li:last-child::after{height:23px}
  ul.or .node{
    border-color:var(--muted);
    box-shadow:none;
    font-weight:400;
  }

  /* --- OPTIONAL (`+`, SPEC §3/D29): gestrichelter Abzweig + hohler Kreis ---
     Gestrichelt wird NUR der Abzweig zum Knoten, nicht die Sammelleiste — die
     gehört der ganzen Geschwistergruppe. Im Fächer ist der Abzweig der
     senkrechte Stiel (border-left von ::after, beim letzten Kind border-right
     von ::before), in den gestapelten Anordnungen der waagerechte Ast
     (border-top von ::before); die Leiste ist jeweils die andere Kante.
     Gestrichelt in TINTE — any-of ist gestrichelt in Grau (D15), die Farbe
     hält beide auseinander, der Kreis unten bestätigt es. */
  ul.and>li.opt::after{border-left-style:dashed}
  ul.and>li.opt:last-child::before{border-right-style:dashed}
  .tree.vertical ul.and>li.opt::before,
  .tree.kompakt ul.and>li.opt::before,
  .tree:not(.vertical):not(.kompakt) ul.or>li.has-and>ul.and>li.opt::before{
    border-top-style:dashed;
  }

  /* Hohler Kreis am Abzweig ---
     Kein dritter Linienstil: gestrichelt gehört any-of, und im kompakten Modus
     codiert allein der Stil das Gate (D15). Stattdessen die FODA-Konvention —
     gefüllter Punkt = erforderlich (hier: gar keiner), hohler Punkt = optional.
     Der Kreis sitzt MITTIG AUF der Knotenkante, wo der Abzweig auftrifft.
     Grundfall ist die gestapelte Anordnung (vertikal, kompakt, unter any-of):
     Abzweig kommt von links auf halber Knotenhöhe. */
  .node.opt::before{
    content:'';position:absolute;
    /* 18px statt 50%: Der gestapelte Abzweig sitzt fest bei 23px im <li>
       (= 5px Listenabstand + halbe EINZEILIGE Knotenhöhe). Seit Knoten
       umbrechen (D64), ist 50% bei mehrzeiligen nicht mehr diese Höhe —
       der Kreis muss auf dem Abzweig sitzen, nicht auf der Knotenmitte.
       Für einzeilige Knoten sind 18px und 50% dasselbe. */
    left:-5px;top:18px;transform:translateY(-50%);
    width:10px;height:10px;border-radius:50%;
    background:var(--card);border:2px solid var(--line);
    box-sizing:border-box;
  }
  /* Horizontaler Fächer: der Abzweig kommt von oben mittig. */
  .tree:not(.vertical):not(.kompakt) ul.and>li>.node.opt::before{
    left:50%;top:-5px;transform:translateX(-50%);
  }
  /* …ausgenommen der gestapelte all-of-Teilbaum unter einer any-of-Gruppe
     (siehe oben): dort kommt der Abzweig wieder von links. */
  .tree:not(.vertical):not(.kompakt) ul.or>li.has-and>ul.and>li>.node.opt::before{
    left:-5px;top:18px;transform:translateY(-50%);
  }
  /* Vertikal zentrierte all-of-Zwischenknoten (unten): dort trifft der Abzweig
     wirklich die Knotenmitte (50%) — der Kreis geht mit. */
  .tree.vertical li.has-and>ul.and>li.has-and>.node.opt::before{top:50%}

  /* --- TREPPE: aufeinanderfolgende optionale Endknoten (D29, Nachtrag 3) ---
     Nur im horizontalen Fächer, wo jedes Geschwister sonst eine eigene Spalte
     kostet. `applyOptStairs()` in app.js gruppiert sie in
     `li.opt-group > ul.opt-stair` und nummeriert die Stufen als `--i` (0-basiert).
     Geometrie: Stufe 0 hängt am Stiel von oben wie ein gewöhnliches Kind
     (`--stem-x` zielt auf sie). Jede weitere Stufe steht `--stair-step` weiter
     rechts und hängt an einem gestrichelten Winkel, der bündig an der linken
     Kante der VORIGEN Stufe herabfällt und waagerecht in den eigenen Kreis
     einbiegt. Das setzt voraus, dass die Zelle so hoch ist wie ihr Knoten —
     deshalb nur Endknoten. */
  .tree:not(.vertical):not(.kompakt) ul.opt-stair{
    --stair-step:26px; --stair-gap:12px;
    display:flex;flex-direction:column;align-items:flex-start;position:relative;
  }
  .tree:not(.vertical):not(.kompakt) ul.opt-stair>li{
    align-items:flex-start;
    padding:var(--stair-gap) 0 0 calc(var(--i) * var(--stair-step));
  }
  .tree:not(.vertical):not(.kompakt) ul.opt-stair>li:first-child{padding-top:0}
  /* Winkel zur vorigen Stufe: senkrecht herab, dann waagerecht in den Knoten.
     Der Knick liegt auf der linken Kante der vorigen Stufe. */
  .tree:not(.vertical):not(.kompakt) ul.opt-stair>li+li::before,
  .tree:not(.vertical):not(.kompakt) ul.opt-stair>li+li::after{
    content:'';position:absolute;
    left:calc((var(--i) - 1) * var(--stair-step));
  }
  .tree:not(.vertical):not(.kompakt) ul.opt-stair>li+li::after{
    top:0;height:calc(var(--stair-gap) + 16px);
    border-left:2px dashed var(--line);
  }
  .tree:not(.vertical):not(.kompakt) ul.opt-stair>li+li::before{
    top:calc(var(--stair-gap) + 16px);width:var(--stair-step);
    border-top:2px dashed var(--line);
  }
  /* Stufe 0 wird von oben getroffen (Kreis oben mittig), alle weiteren von
     links — das ist bereits der Grundfall von `.node.opt::before`. */
  .tree:not(.vertical):not(.kompakt) ul.opt-stair>li:first-child>.node.opt::before{
    left:50%;top:-5px;transform:translateX(-50%);
  }

  /* ---------- Status (Pastell) ---------- */
  .node.st-idee      {background:#EBEDEF;border-color:#A2ABB5;color:var(--ink)}
  .node.st-geplant   {background:#EBE4F6;border-color:#A991D4;color:var(--ink)}
  .node.st-arbeit    {background:#FADDE4;border-color:#D897A8;color:var(--ink)}
  .node.st-durchstich{background:#FBF2CE;border-color:#D9BE63;color:var(--ink)}
  .node.st-fertig    {background:#DCF1DE;border-color:#86C293;color:var(--ink)}
  .node.st-prod      {background:#DBEAF8;border-color:#85ACD7;color:var(--ink)}
  .node.st-highrisk  {background:#FFE5CC;border-color:#F97316;color:var(--ink)}
  .node.st-verworfen{
    background:#F1F2F4;border-color:#B3BAC2;border-style:dashed;
    color:var(--muted);text-decoration:line-through;box-shadow:none;
  }

  .chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
  .chip{
    font-family:'IBM Plex Mono',monospace;font-size:.72rem;
    border:1.5px solid;border-radius:6px;padding:2px 8px;
    color:var(--ink);white-space:nowrap;
  }
  .chip.st-idee      {background:#EBEDEF;border-color:#A2ABB5}
  .chip.st-geplant   {background:#EBE4F6;border-color:#A991D4}
  .chip.st-arbeit    {background:#FADDE4;border-color:#D897A8}
  .chip.st-durchstich{background:#FBF2CE;border-color:#D9BE63}
  .chip.st-fertig    {background:#DCF1DE;border-color:#86C293}
  .chip.st-prod      {background:#DBEAF8;border-color:#85ACD7}
  .chip.st-highrisk  {background:#FFE5CC;border-color:#F97316}
  .chip.st-verworfen{background:#F1F2F4;border-color:#B3BAC2;border-style:dashed;color:var(--muted);text-decoration:line-through}

  /* ---------- Transponierte Modi (vertikal + kompakt) ---------- */
  /* Gemeinsame Basis: Kinder untereinander, Sammelleiste links,
     Abzweig auf Knotenmitte (23-px-Offset). */
  .tree.vertical, .tree.kompakt{margin:0}
  .tree.vertical li, .tree.kompakt li{align-items:flex-start}
  .tree.vertical ul.and, .tree.kompakt ul.and{--rail:9px;flex-direction:column;align-items:flex-start;padding-top:14px}
  .tree.vertical ul.and::before, .tree.kompakt ul.and::before{left:var(--rail);height:14px}
  .tree.vertical ul.and>li, .tree.kompakt ul.and>li{padding:5px 0 5px calc(var(--rail) + 21px)}
  .tree.vertical ul.and>li::before, .tree.kompakt ul.and>li::before{
    left:var(--rail);right:auto;top:23px;width:21px;height:0;
    border:0;border-top:2px solid var(--line);border-radius:0;
  }
  .tree.vertical ul.and>li::after, .tree.kompakt ul.and>li::after{
    left:var(--rail);right:auto;top:0;width:0;height:100%;
    border:0;border-left:2px solid var(--line);border-radius:0;
  }
  .tree.vertical ul.and>li:last-child::after, .tree.kompakt ul.and>li:last-child::after{height:23px}

  /* Nur vertikal: echter transponierter, ZENTRIERTER LR-Baum — analog zur
     horizontalen Darstellung, nur um 90° gedreht. Der Überknoten sitzt
     vertikal mittig zu seiner Kindergruppe; die all-of-Verbindung tritt rechts
     auf halber Höhe aus, eine vertikale Sammelleiste verteilt auf die Kinder.
     Kompakt bleibt unberührt (führt all-of nach unten). */
  .tree.vertical li.has-and{flex-direction:row;align-items:center}
  /* Der Stub ist hier länger als die 9 px der gemeinsamen Basis: Das
     Größen-Badge (oben rechts) und die Tag-Pillen (unten rechts) ragen 8 px
     über die Knotenkante hinaus und standen mit 1 px Luft an der
     Sammelleiste — mit ihrem weißen Rand fraßen sie die Linie optisch auf.
     20 px lassen 12 px Luft. Nur vertikal: kompakt führt all-of nach unten,
     dort treffen Badge und Leiste nie aufeinander. */
  .tree.vertical li.has-and>ul.and{--rail:20px;padding-top:0}
  /* Stub von der Parent-Mitte (halbe Höhe) zur vertikalen Sammelleiste */
  .tree.vertical li.has-and>ul.and::before{
    left:0;top:50%;width:var(--rail);height:0;
    border-left:0;border-top:2px solid var(--line);
  }
  /* Abzweig trifft die tatsächliche Knotenmitte des Kindes:
     Blatt- und Any-of-Kinder sitzen oben (Offset 23 px), all-of-Zwischenknoten
     sind selbst vertikal zentriert (50 %). Ohne diese Unterscheidung liefen die
     Linien zu zentrierten Zwischenknoten ins Leere. Die Enden der Sammelleiste
     folgen demselben Abzweigpunkt des jeweiligen Rand-Kindes. */
  .tree.vertical li.has-and>ul.and>li:first-child::after{top:23px;height:calc(100% - 23px)}
  /* Einziges Kind: KEIN border:0 mehr (D65). Der Eltern-Stub dockt bei 50 % der
     Gruppenhöhe an, der Kind-Abzweig liegt fest bei 23 px — seit dem
     20-px-Zusatzabstand unten (und erst recht bei mehrzeiligen Knoten, D64)
     fallen die beiden NICHT mehr zusammen. Das Verbindungsstück ist hier rein
     in CSS ausdrückbar, weil Gruppenhöhe == Kindhöhe (ul hat padding-top:0). */
  .tree.vertical li.has-and>ul.and>li:only-child::after{top:23px;height:max(0px, calc(50% - 23px))}
  /* Letztes Kind mit großem Teilbaum (D65): Die Leiste endet am 23-px-Abzweig,
     der Eltern-Stub dockt aber bei 50 % der GRUPPE an — liegt der tiefer
     (großer Teilbaum im letzten Kind), hinge er in der Luft. `alignVRails()`
     in app.js misst das und verlängert die Leiste per --vrail-ext bis zum
     Stub. has-and-Kinder brauchen das nie (Abzweig bei 50 % der Zelle, und
     50 % der Gruppe liegt beweisbar darüber). */
  .tree.vertical li.has-and>ul.and>li:last-child:not(.has-and):not(:only-child)::after{height:var(--vrail-ext, 23px)}
  .tree.vertical li.has-and>ul.and>li.has-and::before{top:50%}
  .tree.vertical li.has-and>ul.and>li.has-and:first-child::after{top:50%;height:50%}
  .tree.vertical li.has-and>ul.and>li.has-and:last-child::after{top:0;height:50%}
  .tree.vertical li.has-and>ul.and>li.has-and:only-child::after{border:0}

  /* Mehr Luft zwischen gestapelten Knoten (transponierte Modi): sonst
     überlappt das Größen-Badge (oben rechts) eines Knotens mit den
     Namens-Tags (unten rechts) des Knotens darüber. Nur zusätzlicher Abstand
     NACH unten, damit der obere 23-px-Abzweig unverändert bleibt. */
  .tree.kompakt ul.and>li,
  .tree.kompakt ul.or>li,
  .tree.vertical ul.or>li,
  .tree.vertical ul.and>li:not(.has-and){
    padding-bottom:20px;
  }
  /* Zentrierte all-of-Zwischenknoten (nur vertikal) brauchen den Abstand
     symmetrisch, damit ihr 50%-Abzweig auf der Knotenmitte bleibt. */
  .tree.vertical li.has-and>ul.and>li.has-and{
    padding-top:16px;padding-bottom:16px;
  }
  /* Vertikal + any-of-Alternative, die selbst all-of zerlegt (z. B. „Web+Nativ“):
     Der Knoten sitzt vertikal mittig zu seinem Rechts-Fächer, also muss auch der
     EINKOMMENDE any-of-Abzweig auf die Knotenmitte (50 %) zeigen statt auf 23 px —
     sonst ist der Knoten von seiner Linie von oben abgetrennt. Symmetrisches
     Padding hält die Mitte bei 50 % (überschreibt das asymmetrische ul.or>li). */
  .tree.vertical ul.or>li.has-and{padding-top:16px;padding-bottom:16px}
  .tree.vertical ul.or>li.has-and::before{top:50%}
  .tree.vertical ul.or>li.has-and:last-child::after{height:50%}

  /* Kompakt: auch All-of läuft nach unten (spart Breite) — kein Rechts-Fächer.
     Die graue, gestrichelte Any-of-Führung kommt aus der Basis und gilt hier
     wie in allen Modi. Gate-Codierung: und = durchgezogen dunkel (Tinte),
     oder = gestrichelt grau. */

  /* ---------- Kleiner Bildschirm (Smartphone) ----------
     body.mobile wird per matchMedia (<=640px) gesetzt. Es ist **immer genau ein
     Bereich** zu sehen (Diagramm ODER Text); oben links schaltet ein Knopf um.
     Die Min/Normal/Max-Buttons und der Splitter entfallen hier ganz — es gibt
     nichts zu teilen. Die Legende bekommt einen eigenen Umschalter statt der
     dauerhaften „AGENDA“-Zeile. Siehe D17-Nachtrag. */
  body.mobile .winbtns{display:none}
  /* Ein Bereich füllt die Fläche; der andere ist `display:none` (nicht bloß
     zusammengeschoben) — nur so bekommt der sichtbare die ganze Höhe, ohne
     dass eine zweite Titelzeile sie anknabbert. Die Kehrseite steht in
     app.js: Ein verborgenes Diagramm misst sich zu 0, deshalb wird die
     Geometrie beim Zurückschalten neu gezeichnet. */
  body.mobile .app,
  body.mobile .app.side{
    grid-template-columns:minmax(0,1fr);
    grid-template-rows:minmax(0,1fr);
    grid-template-areas:"pane";
  }
  body.mobile .gutter{display:none}
  body.mobile .panel.editor,
  body.mobile .panel.right{grid-area:pane}
  body.pane-diagram.mobile .panel.editor{display:none}
  body.pane-text.mobile .panel.right{display:none}
  /* Der Bereichstitel „DIAGRAMM" entfällt — welcher Bereich vorn ist, sagt der
     Inhalt; die Zeile wird für den Umschalter und die Aktionen gebraucht. */
  body.mobile .panel.right .panel-head>span[data-i18n="diagramTitle"]{display:none}
  /* Umschalter oben links; er übernimmt vom entfallenen Titel das
     `margin-right:auto`, damit die Aktions-Knöpfe rechts stehen bleiben. Im
     Editor-Kopf tut das weiterhin der Dokumenten-Wähler daneben. */
  body.mobile .panetoggle{display:inline-flex}
  body.mobile .panel.right .panetoggle{margin-right:auto}
  /* Die Titelzeile muss EINZEILIG bleiben (D17): Bricht sie um, wächst der
     Kopf auf das Doppelte und frisst die Fläche, für die es den
     Ein-Bereich-Modus überhaupt gibt. Die Knöpfe passen längst — es waren die
     Abstände: 14 px Lücke mal sieben plus 2×16 px Innenabstand sind mehr als
     ein Drittel von 375 px. Deshalb hier enger und `nowrap` als Riegel; die
     Knöpfe selbst bleiben unangetastet (Fingergröße).
     Mit dem neunten Bedienelement (Stationssprung, D47) noch einmal enger:
     6 px Lücke und 8 px Innenabstand. Dazu `flex:0 0 auto` — ohne das schrumpft
     ausgerechnet der Modus-Wähler (das einzige Element ohne feste Größe) bei
     Platzmangel auf einen 2-px-Strich zusammen, während sein Icon darüber
     hinausragt. Ein zerdrücktes Bedienelement ist der schlechtere Fehler als
     ein sichtbarer Überlauf (D17-Nachtrag 5). */
  body.mobile .panel-head{gap:6px;flex-wrap:nowrap;padding-inline:8px}
  body.mobile .panel-head>*{flex:0 0 auto}
  /* Unterhalb von rund 440 px ist es reine Arithmetik: **zehn** Bedienelemente
     à 29 px (D56 hat den ID-Umschalter hinzugefügt) plus Lücken brauchen
     386 px, bei 375 px stehen 359 zur Verfügung. Kleiner als 29 px darf ein
     Fingerziel nicht werden (D17-Nachtrag 5) — dort darf die Zeile deshalb
     umbrechen. Sie tut es aber nur, wenn wirklich alle zehn da sind: Der
     „Was ist neu?"-Knopf (44 px) erscheint nur bei Neuigkeiten, ohne ihn
     bleiben 336 px und damit eine Reihe. Die Schwelle stand vorher bei 360 px
     und ist mit dem zehnten Element mitgewandert — dieselbe Rechnung, ein
     Element mehr.
     **Kein `overflow` an dieser Stelle** (D50): Die Kopfzeile trägt die beiden
     Aufklapp-Menüs (Dokumente, Download) als absolut positionierte Kinder. Ein
     Scroll-Container klippt sie — und weil `overflow-x` das `visible` der
     anderen Achse auf `auto` hebt, klippt er auch nach unten, wo die Menüs
     stehen. Sie waren damit auf **jeder** Mobilbreite unerreichbar, nicht nur
     auf den schmalen. */
  @media (max-width:440px){
    body.mobile .panel-head{flex-wrap:wrap}
  }
  /* Inhalte rund 25 % kleiner, damit mehr Plan auf die Fläche passt
     (D17-Nachtrag 2). Das Diagramm macht das über den CSS-`zoom` (Faktor auf
     den Nutzer-Zoom, siehe MOBILE_ZOOM in app.js); der Text über die
     Schriftgröße — `zoom` verbietet sich hier, weil Zeilennummern-Streifen und
     Spiegel am Textfeld messen (D33) und der Streifen seine Breite in `ch`
     rechnet. Beide müssen dieselbe Typografie tragen, sonst wandern die Zahlen
     weg; `line-height` ist einheitenlos und skaliert von selbst mit. */
  body.mobile textarea,
  body.mobile .lineno{font-size:.64rem}
  body.mobile textarea{padding:10px 12px}
  /* 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}
  /* Das Neuigkeiten-Popup ist breiter als sein Knopf. Am Knopf ausgerichtet
     (`.newswrap{position:relative}`) begänne es auf dem Telefon bei −116 px,
     also außerhalb des Bildes — nachgemessen bei 375 px. Hier zählt deshalb
     die **Werkzeugleiste** als Bezug: Ihr rechter Rand ist auch der der Seite.
     Kein `overflow` an der Kopfzeile als Ausweg — das klippt die Menüs (D50). */
  body.mobile .newswrap{position:static}
  body.mobile .langsel button[data-lang]{display:none}
  body.mobile .langsel button[data-lang].active{display:inline-block}
  body.mobile .langmore{display:none}
  body.mobile .langsel.expanded{
    position:absolute;top:0;right:0;z-index:40;
    background:var(--card);
    box-shadow:0 6px 18px rgba(36,52,71,.28);
  }
  body.mobile .langsel.expanded button[data-lang]{display:inline-block}
  /* Modus-Wähler kompakt: nur das aktive Icon (ein Drittel Platz), Tippen
     schaltet reihum weiter (horizontal → kompakt → vertikal → …, siehe JS). */
  body.mobile .seg label{display:none}
  body.mobile .seg label:has(input:checked){display:flex}
  /* Download schlank: nur der Trigger-Icon-Button; ein Tipp klappt SVG/PNG als
     Dropdown-Overlay unter der Kopfzeile auf (verhindert den Zeilenumbruch durch
     die breiten „SVG“/„PNG“-Labels). Kopieren bleibt sichtbar. */
  body.mobile .dlgroup{position:relative}
  body.mobile .dltrigger{display:inline-flex}
  body.mobile .dltrigger[aria-expanded="true"]{border-color:var(--or);color:var(--or)}
  body.mobile .dlmenu{display:none}
  body.mobile .dlgroup.open .dlmenu{
    display:flex;flex-direction:column;gap:5px;
    position:absolute;top:calc(100% + 6px);right:0;z-index:40;
    background:var(--card);border:1px solid rgba(36,52,71,.15);
    border-radius:8px;padding:5px;box-shadow:0 6px 18px rgba(36,52,71,.28);
  }

  /* ---------- Druck: nur das Diagramm (SPEC §9, TASKS Phase 2) ----------
     Editor-Panel, Bedienelemente, Splitter, Kopfzeile, Warnungen und Footer
     verschwinden; das Diagramm füllt die Seite und darf über mehrere Seiten
     laufen. Statusfarben werden bewusst mitgedruckt (Browser strippen
     Hintergründe sonst). Der Günstigster-Pfad-Overlay liegt in #out und wird
     mitgedruckt. */
  @media print{
    html,body{height:auto!important;overflow:visible!important;background:#fff!important}
    header,.panel.editor,.gutter,.panel.right .panel-head,
    .warnings,.zoomctl,.site-footer,.nodetip,.aclist{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).
       Bei der Cursor-Zeile gehören Erhebung und Puls mit dazu — sonst stünde ein
       Knoten im Druck grundlos vergrößert und schief zu seiner Anschlusslinie. */
    .node.current,.node.focusmark{box-shadow:none!important}
    .node.current{transform:none!important;animation:none!important}
    .node.current::after{display:none!important}
    .node.fresh{box-shadow:none!important}     /* „neu seit dem letzten Besuch" ist persönlich (D28) */
    .node.tipped{box-shadow:none!important}    /* offenes Knoten-Fenster ist Bedienung (D52) */
    /* Faltung (D38): das ▾ offener Knoten ist Bedienelement — nicht drucken;
       das „▸ n" eingeklappter Knoten ist Kennzeichnung und bleibt. */
    .node:not(.folded) .fold{display:none!important}
    @page{margin:12mm}
  }

/* ---------- Konflikt beim gemeinsamen Bearbeiten (D76) ----------
   Band oben, wie der Update-Hinweis: Es unterbricht nicht die Arbeit, steht
   aber über allem, bis jemand entscheidet. In `--warn`, nicht in Petrol — es
   ist keine Einladung, sondern eine Sache, die geklärt werden muss. */
.live-conflict{
  position: fixed; inset: 0 0 auto 0; z-index: 1200;
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; padding: 10px 16px;
  background: var(--warn, #B45309); color: #fff;
  font: 500 14px/1.4 'IBM Plex Sans', system-ui, sans-serif;
  box-shadow: 0 2px 8px rgba(0,0,0,.25);
}
.live-conflict-actions{ display: flex; gap: 8px; flex: 0 0 auto; }
.live-conflict button{
  font: 500 13px/1 'IBM Plex Sans', system-ui, sans-serif;
  padding: 6px 10px; border-radius: 4px; cursor: pointer;
  border: 1px solid #fff; background: transparent; color: #fff;
}
/* „Eigene durchsetzen" ist die Antwort, die nichts von der eigenen Arbeit
   kostet - sie steht deshalb gefüllt da, ohne die andere zu verstecken. */
.live-conflict button.mine{ background: #fff; color: var(--warn, #B45309); border-color: #fff; }
.live-conflict button:focus-visible{ outline: 2px solid #fff; outline-offset: 2px; }
@media print{ .live-conflict{ display: none; } }

/* Server-Dokumente im Wähler: der Host neben dem Namen (D76). Ein
   hochgeladenes Dokument heißt wie das lokale, aus dem es entstand — ohne
   diesen Zusatz stünden zwei gleiche Einträge da. Zurückgenommen, denn der
   Name bleibt die Hauptaussage. */
.docitem-where{
  margin-left: auto; padding-left: 10px;
  font-size: .82em; color: var(--muted); white-space: nowrap;
}
.docitem[aria-checked="true"] .docitem-where{ color: inherit; opacity: .75; }
